diff --git a/.cursor/rules/ai-read.mdc b/.cursor/rules/ai-read.mdc
index 5929781..a7a7f08 100644
--- a/.cursor/rules/ai-read.mdc
+++ b/.cursor/rules/ai-read.mdc
@@ -37,6 +37,12 @@ alwaysApply: true
- 使用 pinia element-plus typescript vite Axios dayjs vueuse
- Using dynamic import() to code-split the application
+### 最新架构说明(2025-11-29)
+- 旧版 UI 迁移到 `vue-legacy/`,新 `vue/` 目录仅保留:仪表板、全部邮件、转发管理、系统设置、调试模式、统一登录页,继续沿用动态加载与统一刷新机制。
+- 项目切换到 **单管理员模式**:系统中只存在一个管理员账号,用户注册、邮箱申请/私信、用户管理、安全概览等功能全部移除。
+- `/api/auth/register`、`/api/admin/**`、`/api/mailboxes/**`、`/api/user-info/**`、`/api/security-audit/**` 等接口均已下线,请以 `api/api-doc.yml` 最新版为准。
+- 登录页仍根据 `allow_registration` 字段展示提示,但该字段只读,用于未来是否重新开放注册的判断;后端始终拒绝注册请求。
+
### 📋 开发规则
1. **数据库操作** - 无需复杂迁移,直接修改 `db/schema.sql` 后执行 `npm run db:init`
@@ -57,9 +63,9 @@ alwaysApply: true
### 🚀 开发环境
#### 环境配置
-- **前端**:使用 Docker 容器运行,通过 `docker-compose up -d` 启动
-- **后端**:使用 Docker 容器运行,通过 `docker-compose up -d` 启动
-- **数据库**:通过 Docker 容器执行数据库操作,避免字符编码问题
+- **前端**:在宿主机运行 `cd vue && npm run dev`
+- **后端**:在宿主机运行 `cd worker && npx wrangler dev`
+- **数据库**:直接使用 `wrangler d1` 命令执行操作
#### 目录结构
@@ -82,7 +88,6 @@ alwaysApply: true
│ ├── deploy.js 完整部署脚本
│ ├── dev.js 开发环境启动
│ └── db.js 数据库操作脚本
-└── docker-compose.yml Docker 配置
```
### 前端目录结构介绍
@@ -94,16 +99,13 @@ MainLayoutView负责 登录后的页面展示
#### 开发命令
```bash
-# 启动开发环境
-docker-compose up -d
-
-# 查看容器状态
-docker-compose ps
+# 启动前端
+cd vue && npm run dev
-# 停止容器
-docker-compose down
+# 启动 Worker
+cd worker && npx wrangler dev
-# 数据库操作(通过 Docker 容器执行)
+# 数据库操作
npm run db:init # 初始化数据库
npm run db:init:remote # 初始化远程数据库
npm run db:migrate # 执行数据库迁移
@@ -117,11 +119,11 @@ npm run clean # 清理所有资源
#### 数据库操作
-数据库操作通过 Docker 容器执行:
-- 使用 `docker exec -it worker zsh -c "node scripts/db.js 'SELECT * FROM users'"` 执行 SQL 命令
-- 使用 `docker exec -it worker zsh -c "node scripts/db.js U0VMRUNUICogRlJPTSB1c2Vycwo="` 执行 base64 编码的 SQL
-- 使用 `docker exec -it worker zsh -c "node scripts/db.js init"` 初始化数据库
-- 使用 `docker exec -it worker zsh -c "node scripts/db.js init --remote"` 初始化远程数据库
+数据库操作直接通过 npm 脚本执行:
+- 使用 `npm run db -- "SELECT * FROM users"` 执行 SQL 命令
+- 使用 `npm run db -- U0VMRUNUICogRlJPTSB1c2Vycwo=` 执行 base64 编码的 SQL
+- 使用 `npm run db:init` 初始化数据库
+- 使用 `npm run db:init:remote` 初始化远程数据库
- 所有数据库操作都通过修改 `db/schema.sql` 完成,无需额外迁移脚本
#### 故障排除
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 496d630..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,22 +0,0 @@
-FROM node:slim
-
-# 强制指定 npm 安装路径
-RUN npm config set registry https://registry.npmmirror.com \
- && npm i -g wrangler
-
-RUN apt-get -qq update \
- && apt-get -qq install zsh curl git procps net-tools -y \
- && apt-get -qq autoclean \
- && rm -rf /var/lib/apt/lists/*
-
-# 安装 zsh 和插件
-RUN zsh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" \
- && git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting \
- && git clone https://github.com/zsh-users/zsh-autosuggestions.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions \
- && sed -i "s/plugins=(git)/plugins=(git zsh-syntax-highlighting zsh-autosuggestions docker kubectl brew golang history nmap node npm pip pipenv pyenv pylint python screen sublime)/g" ~/.zshrc
-
-EXPOSE 8787 5173
-CMD []
-ENTRYPOINT ["/usr/bin/zsh"]
-
-# docker build -t node:cem --progress=plain .
diff --git a/api/api-doc.yml b/api/api-doc.yml
index 199ebac..a5c246f 100644
--- a/api/api-doc.yml
+++ b/api/api-doc.yml
@@ -1,50 +1,14 @@
openapi: 3.0.3
info:
title: Cloudflare 邮箱管理系统 API
+ version: 3.0.0
description: |
- 基于 Cloudflare Workers 的邮箱管理系统,提供完整的邮件接收、存储、发送、转发和管理功能。
+ Cloudflare Email Manager (CEM) 的统一 API 规范。系统已精简为 **单管理员模式**,不再开放多用户注册与邮箱申请,所有管理操作均由管理员完成。
- ## 核心设计
- - 所有资源接口保持统一路径
- - 管理员与普通用户通过后端鉴权区分,不再分叉路径
- - 错误响应、分页参数统一复用
- - 邮件发送接口统一 `/api/emails/send`
- - 邮件查询支持 `scope` 参数控制(all/user)
- - 转发规则查询支持 `scope` 参数控制(all/user)
-
- ### 邮件
- ```http
- GET /api/emails # 获取邮件(scope=all/user)
- GET /api/emails/{id} # 获取详情
- DELETE /api/emails/{id} # 删除邮件
- GET /api/emails/{id}/attachments/{attachmentId} # 下载附件
- POST /api/emails/send # 发送邮件(管理员/用户都能用)
- ```
-
- ### 转发规则
- ```http
- GET /api/forward-rules # 获取转发规则(scope=all/user)
- GET /api/forward-rules/{id} # 获取详情
- POST /api/forward-rules # 创建规则
- PUT /api/forward-rules/{id} # 更新规则
- DELETE /api/forward-rules/{id} # 删除规则
- ```
-
- ## 核心逻辑
-
- 资源路径不区分角色 → 统一 /api/emails, /api/forward-rules
-
- 角色差异用参数控制 → scope=all/user
-
- 业务差异前端封装 → send-info 只是 send 的一层包装
-
- version: 2.0.0
- contact:
- name: XRSec
- email: admin@example.com
- license:
- name: MIT
- url: https://opensource.org/licenses/MIT
+ - 所有接口前缀统一为 `/api`
+ - 统一使用 HttpOnly Cookie 维持会话
+ - localStorage 仅缓存 UI 状态,业务数据一律来自接口
+ - `allow_registration` 字段在健康检查与系统配置中继续返回,用于未来扩展(当前永远为只读)
servers:
- url: http://localhost:8787
@@ -53,49 +17,30 @@ servers:
description: 生产环境
tags:
- - name: 认证
- description: 用户登录、注册、登出等认证相关操作
- - name: 用户
- description: 用户信息管理
- - name: 邮件
- description: 邮件查看、发送、删除、附件下载等操作
- - name: 邮箱
- description: 邮箱申请、管理、审核等功能
- - name: 转发规则
- description: 邮件转发与 Webhook 管理
- - name: 系统
- description: 系统配置与健康检查
- - name: 调试
- description: 调试和开发相关功能
+ - name: Auth
+ description: 登录 / 登出
+ - name: User
+ description: 管理员个人信息维护
+ - name: Emails
+ description: 邮件收取、查看、删除、附件下载
+ - name: ForwardRules
+ description: 全局转发与 webhook 管理
+ - name: System
+ description: 健康检查与系统配置
+ - name: Database
+ description: 调试模式下的数据库辅助接口
paths:
- # ==================== 认证 ====================
/api/auth/login:
post:
- tags:
- - 认证
- summary: 用户登录
- description: 使用用户名和密码进行登录,返回 JWT token。登录成功后需要调用 `/api/users/me` 获取用户详细信息。
+ tags: [Auth]
+ summary: 管理员登录
requestBody:
required: true
content:
application/json:
schema:
- type: object
- required:
- - username
- - password
- properties:
- username:
- type: string
- minLength: 3
- example: admin
- description: 用户名
- password:
- type: string
- minLength: 6
- description: 邮箱密码
- example: 1S*Etuh1pjM$7g
+ $ref: '#/components/schemas/LoginRequest'
responses:
'200':
description: 登录成功
@@ -103,50 +48,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/LoginResponse'
- examples:
- success:
- summary: 登录成功示例
- value:
- success: true
- message: "登录成功"
- data:
- token: "xxxxx.xxxx.xxx"
- user:
- id: 1
- username: "admin"
- user_type: "admin"
- webhook_url: null
- created_at: "2025-09-12 18:03:08"
- '401':
- description: 认证失败
- content:
- application/json:
- schema:
- type: object
- properties:
- success:
- type: boolean
- example: false
- message:
- type: string
- example: 用户不存在或密码错误
- examples:
- wrong_password:
- summary: 密码错误
- value:
- success: false
- message: "用户不存在或密码错误"
- user_not_found:
- summary: 用户不存在
- value:
- success: false
- message: "用户不存在或密码错误"
+ '401': { $ref: '#/components/responses/UnauthorizedError' }
/api/auth/logout:
post:
- tags:
- - 认证
- summary: 用户登出
+ tags: [Auth]
+ summary: 退出登录
security:
- cookieAuth: []
responses:
@@ -155,113 +62,25 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/SuccessResponse'
- examples:
- success:
- summary: 登出成功示例
- value:
- success: true
- message: "登出成功"
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
- /api/auth/register:
- post:
- tags:
- - 认证
- summary: 用户注册
- description: 注册新用户
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- required:
- - username
- - password
- - email
- properties:
- username:
- type: string
- minLength: 3
- example: admin
- description: 用户名
- password:
- type: string
- minLength: 6
- description: 邮箱密码
- example: 1S*Etuh1pjM$7g
- email:
- type: string
- format: email
- responses:
- '201':
- description: 注册成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SuccessResponse'
- '400':
- description: 请求参数错误
- '403':
- description: 系统不允许注册
- '409':
- description: 用户已存在
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
+ $ref: '#/components/schemas/BasicResponse'
- # ==================== 用户 ====================
/api/users/me:
get:
- tags:
- - 用户
- summary: 获取当前用户信息
- description: 获取当前用户信息 以及设置信息
+ tags: [User]
+ summary: 获取管理员信息
security:
- cookieAuth: []
responses:
'200':
- description: 获取成功
+ description: 当前管理员信息
content:
application/json:
schema:
- $ref: '#/components/schemas/UserProfile'
- examples:
- admin_user:
- summary: 管理员用户信息
- value:
- id: 1
- username: "admin"
- email: "admin@example.com"
- user_type: "admin"
- created_at: "2025-09-12 18:03:08"
- updated_at: "2025-09-12 18:09:44"
- settings:
- webhook_url: null
- webhook_secret: null
- normal_user:
- summary: 普通用户信息
- value:
- id: 2
- username: "user1"
- email: "user1@example.com"
- user_type: "user"
- created_at: "2025-09-12 18:05:00"
- updated_at: "2025-09-12 18:05:00"
- settings:
- webhook_url: "https://hooks.slack.com/services/xxx"
- webhook_secret: "secret123"
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
+ $ref: '#/components/schemas/UserProfileResponse'
+ '401': { $ref: '#/components/responses/UnauthorizedError' }
put:
- tags:
- - 用户
- summary: 更新当前用户信息
- description: 包含密码更新、Webhook 设置等
+ tags: [User]
+ summary: 更新管理员信息
security:
- cookieAuth: []
requestBody:
@@ -276,250 +95,193 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/SuccessResponse'
- '400':
- description: 请求参数错误
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
- /api/users:
- get:
- tags:
- - 用户
- summary: 获取用户列表(仅管理员)
- security:
- - cookieAuth: []
- parameters:
- - $ref: '#/components/parameters/PageParam'
- - $ref: '#/components/parameters/LimitParam'
- - name: query
- in: query
- schema:
- type: string
- description: 搜索关键词(用户名、邮箱)
- responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UserListResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- '403':
- $ref: '#/components/responses/ForbiddenError'
-
- post:
- tags:
- - 用户
- summary: 管理员创建用户
- security:
- - cookieAuth: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/AdminUserCreateRequest'
- responses:
- '201':
- description: 创建成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UserResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- '403':
- $ref: '#/components/responses/ForbiddenError'
-
- /api/users/{id}:
- get:
- tags:
- - 用户
- summary: 获取指定用户信息(仅管理员)
- security:
- - cookieAuth: []
- parameters:
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UserProfile'
- '404':
- $ref: '#/components/responses/NotFoundError'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- '403':
- $ref: '#/components/responses/ForbiddenError'
+ $ref: '#/components/schemas/BasicResponse'
+ '400': { $ref: '#/components/responses/BadRequest' }
- delete:
- tags:
- - 用户
- summary: 删除用户(仅管理员)
- security:
- - cookieAuth: []
- parameters:
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: 删除成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SuccessResponse'
- '404':
- $ref: '#/components/responses/NotFoundError'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- '403':
- $ref: '#/components/responses/ForbiddenError'
- # ==================== 邮件 ====================
/api/emails:
get:
- tags:
- - 邮件
+ tags: [Emails]
summary: 获取邮件列表
- description: |
- - 普通用户:默认只能获取自己的邮件,可使用 scope=user 明确指定
- - 管理员:可以使用 scope=all 获取全部邮件,或 scope=user 获取自己的邮件
security:
- cookieAuth: []
parameters:
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/LimitParam'
- - name: scope
- in: query
- description: 查询范围,all=所有邮件(需管理员权限),user=当前用户邮件,不传则默认返回当前用户邮件
- required: false
- schema:
- type: string
- enum: [all, user]
- name: search
in: query
- schema:
- type: string
- description: 搜索关键词
+ schema: { type: string }
+ description: 主题 / 发件人模糊搜索
- name: status
in: query
- schema:
- type: string
- enum: [received, processed, forwarded, failed]
- description: 邮件状态
+ schema: { type: string, enum: ['unread','read'] }
+ - name: scope
+ in: query
+ schema: { type: string, enum: ['all'] }
+ description: 仅管理员可传 `all`
responses:
'200':
- description: 获取成功
+ description: 邮件列表
content:
application/json:
schema:
$ref: '#/components/schemas/EmailListResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
+ '401': { $ref: '#/components/responses/UnauthorizedError' }
/api/emails/{id}:
get:
- tags:
- - 邮件
+ tags: [Emails]
summary: 获取邮件详情
security:
- cookieAuth: []
parameters:
- - name: id
- in: path
- required: true
- schema:
- type: string
+ - $ref: '#/components/parameters/EmailIdParam'
responses:
'200':
- description: 获取成功
+ description: 邮件详情
content:
application/json:
schema:
- $ref: '#/components/schemas/EmailDetail'
- '404':
- $ref: '#/components/responses/NotFoundError'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
+ $ref: '#/components/schemas/EmailDetailResponse'
+ '404': { $ref: '#/components/responses/NotFoundError' }
delete:
- tags:
- - 邮件
+ tags: [Emails]
summary: 删除邮件
security:
- cookieAuth: []
parameters:
- - name: id
- in: path
- required: true
- schema:
- type: string
+ - $ref: '#/components/parameters/EmailIdParam'
+ responses:
+ '200': { $ref: '#/components/schemas/BasicResponse' }
+ '404': { $ref: '#/components/responses/NotFoundError' }
+
+ /api/emails/{id}/raw:
+ get:
+ tags: [Emails]
+ summary: 下载原始邮件(.eml 格式,支持 HTTP 缓存)
+ description: |
+ 获取邮件的原始 RFC 822 格式内容,支持完整的 HTTP 缓存机制:
+ - 返回剔除附件后的完整邮件(包含邮件头和正文)
+ - **ETag**: 基于邮件 ID 和更新时间生成
+ - **Last-Modified**: 邮件的最后修改时间
+ - **Cache-Control**: public, max-age=31536000, immutable(缓存 1 年)
+ - **304 Not Modified**: 当客户端缓存有效时返回
+ security:
+ - cookieAuth: []
+ parameters:
+ - $ref: '#/components/parameters/EmailIdParam'
+ - name: If-None-Match
+ in: header
+ required: false
+ description: ETag 值,用于条件请求
+ schema: { type: string }
+ - name: If-Modified-Since
+ in: header
+ required: false
+ description: 最后修改时间,用于条件请求
+ schema: { type: string }
responses:
'200':
- description: 删除成功
- content:
- application/json:
+ description: 原始邮件内容(RFC 822 格式)
+ headers:
+ ETag:
+ description: 邮件的 ETag 标识
+ schema: { type: string }
+ Last-Modified:
+ description: 邮件的最后修改时间
+ schema: { type: string }
+ Cache-Control:
+ description: 缓存策略
+ schema: { type: string }
+ example: 'public, max-age=31536000, immutable'
+ content:
+ message/rfc822:
schema:
- $ref: '#/components/schemas/SuccessResponse'
- '404':
- $ref: '#/components/responses/NotFoundError'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
+ type: string
+ format: binary
+ '304':
+ description: Not Modified(缓存仍然有效)
+ headers:
+ ETag:
+ schema: { type: string }
+ Last-Modified:
+ schema: { type: string }
+ '404': { $ref: '#/components/responses/NotFoundError' }
/api/emails/{id}/attachments/{attachmentId}:
get:
- tags:
- - 邮件
- summary: 下载邮件附件
+ tags: [Emails]
+ summary: 下载附件(支持 HTTP 缓存)
+ description: |
+ 下载邮件附件,支持完整的 HTTP 缓存机制:
+ - **ETag**: 基于附件 ID 和更新时间生成
+ - **Last-Modified**: 附件的最后修改时间
+ - **Cache-Control**: public, max-age=31536000, immutable(缓存 1 年)
+ - **304 Not Modified**: 当客户端缓存有效时返回
+
+ **条件请求支持**:
+ - 客户端可发送 `If-None-Match` 头(ETag 值)
+ - 客户端可发送 `If-Modified-Since` 头(时间戳)
+ - 如果缓存有效,服务器返回 304,无响应体
security:
- cookieAuth: []
parameters:
- - name: id
- in: path
- required: true
- schema:
- type: string
+ - $ref: '#/components/parameters/EmailIdParam'
- name: attachmentId
in: path
required: true
- schema:
- type: string
+ schema: { type: integer }
+ - name: If-None-Match
+ in: header
+ required: false
+ description: ETag 值,用于条件请求
+ schema: { type: string }
+ example: '"abc123"'
+ - name: If-Modified-Since
+ in: header
+ required: false
+ description: 最后修改时间,用于条件请求
+ schema: { type: string }
+ example: 'Sun, 30 Nov 2025 12:00:00 GMT'
responses:
'200':
- description: 下载成功
- content:
- application/octet-stream:
- schema:
- type: string
- format: binary
- '404':
- $ref: '#/components/responses/NotFoundError'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
+ description: 附件文件流(首次请求或缓存失效)
+ headers:
+ ETag:
+ description: 附件的 ETag 标识
+ schema: { type: string }
+ example: '"abc123"'
+ Last-Modified:
+ description: 附件的最后修改时间
+ schema: { type: string }
+ example: 'Sun, 30 Nov 2025 12:00:00 GMT'
+ Cache-Control:
+ description: 缓存策略
+ schema: { type: string }
+ example: 'public, max-age=31536000, immutable'
+ Vary:
+ description: 缓存隔离策略
+ schema: { type: string }
+ example: 'Authorization'
+ content:
+ '*/*': {}
+ '304':
+ description: Not Modified(缓存仍然有效)
+ headers:
+ ETag:
+ description: 附件的 ETag 标识
+ schema: { type: string }
+ Last-Modified:
+ description: 附件的最后修改时间
+ schema: { type: string }
+ Cache-Control:
+ description: 缓存策略
+ schema: { type: string }
+ '404': { $ref: '#/components/responses/NotFoundError' }
/api/emails/send:
post:
- tags:
- - 邮件
- summary: 发送邮件
- description: |
- 通用发送接口:
- - 普通用户:可以发邮件(如果系统允许)
- - 管理员:可用来向用户发送账户信息(前端封装即可)
+ tags: [Emails]
+ summary: 发送邮件(调试)
security:
- cookieAuth: []
requestBody:
@@ -527,151 +289,31 @@ paths:
content:
application/json:
schema:
- type: object
- required:
- - to
- - subject
- - content
- properties:
- to:
- type: string
- format: email
- description: 收件人邮箱
- from:
- type: string
- format: email
- description: 发件人邮箱(不传默认使用系统邮箱)
- subject:
- type: string
- description: 邮件主题
- content:
- type: string
- description: 邮件正文
- content_type:
- type: string
- enum: [text, html, markdown]
- default: markdown
+ $ref: '#/components/schemas/SendEmailRequest'
responses:
- '200':
- description: 发送成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SuccessResponse'
- '400':
- description: 请求参数错误
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- # ==================== 邮箱 ====================
- /api/mailboxes:
+ '200': { $ref: '#/components/schemas/BasicResponse' }
+ '400': { $ref: '#/components/responses/BadRequest' }
+
+ /api/forward-rules:
get:
- tags:
- - 邮箱
- summary: 获取邮箱列表
- description: |
- - 默认行为:始终返回当前用户的邮箱
- - 当 scope=all 时:需要管理员权限,返回所有邮箱
- - 当 scope=user 时:明确指定返回当前用户的邮箱
- - 支持分页查询
+ tags: [ForwardRules]
+ summary: 获取转发规则
security:
- cookieAuth: []
parameters:
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/LimitParam'
- - name: scope
- in: query
- description: 查询范围,all=所有邮箱(需管理员权限),user=当前用户邮箱,不传则默认返回当前用户邮箱
- required: false
- schema:
- type: string
- enum: [all, user]
- example: all
responses:
'200':
- description: 获取成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/MailboxListResponse'
- examples:
- user_mailboxes:
- summary: 普通用户邮箱列表
- description: 普通用户只能看到自己的邮箱
- value:
- total: 2
- items:
- - id: 1
- owner_id: 2
- owner_username: "user1"
- owner_usertype: 0
- address: "user1@example.com"
- status: "active"
- created_at: "2025-09-12 18:05:00"
- updated_at: "2025-09-12 18:05:00"
- - id: 2
- owner_id: 2
- owner_username: "user1"
- owner_usertype: 0
- address: "test@example.com"
- status: "active"
- created_at: "2025-09-12 18:06:00"
- updated_at: "2025-09-12 18:06:00"
- admin_mailboxes:
- summary: 管理员查看所有邮箱
- description: 管理员需要看到所有用户的邮箱信息
- value:
- total: 5
- items:
- - id: 1
- owner_id: 1
- owner_username: "admin"
- owner_usertype: 1
- address: "admin@example.com"
- status: "active"
- created_at: "2025-09-12 18:03:08"
- updated_at: "2025-09-12 18:03:08"
- - id: 2
- owner_id: 2
- owner_username: "user1"
- owner_usertype: 0
- address: "user1@example.com"
- status: "active"
- created_at: "2025-09-12 18:05:00"
- updated_at: "2025-09-12 18:05:00"
- - id: 3
- owner_id: 2
- owner_username: "user1"
- owner_usertype: 0
- address: "test@example.com"
- status: "disabled"
- created_at: "2025-09-12 18:06:00"
- updated_at: "2025-09-12 18:06:00"
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- '403':
- description: 权限不足
+ description: 规则列表
content:
application/json:
schema:
- $ref: '#/components/schemas/ErrorResponse'
- examples:
- insufficient_permission:
- summary: 权限不足
- value:
- success: false
- error: "权限不足,只有管理员可以查看所有邮箱"
-
+ $ref: '#/components/schemas/ForwardRuleListResponse'
+ '403': { $ref: '#/components/responses/ForbiddenError' }
post:
- tags:
- - 邮箱
- summary: 创建邮箱
- description: |
- - 普通用户:提交申请
- - 管理员:直接创建邮箱
+ tags: [ForwardRules]
+ summary: 创建转发规则
security:
- cookieAuth: []
requestBody:
@@ -679,190 +321,133 @@ paths:
content:
application/json:
schema:
- oneOf:
- - $ref: '#/components/schemas/MailboxApplicationRequest'
- - $ref: '#/components/schemas/AdminMailboxCreateRequest'
+ $ref: '#/components/schemas/ForwardRuleInput'
responses:
- '201':
- description: 创建/申请成功
- content:
- application/json:
- schema:
- oneOf:
- - $ref: '#/components/schemas/MailboxApplicationResponse'
- - $ref: '#/components/schemas/MailboxResponse'
- '400':
- description: 请求参数错误
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
+ '200': { $ref: '#/components/schemas/ForwardRuleResponse' }
+ '400': { $ref: '#/components/responses/BadRequest' }
- /api/mailboxes/{id}:
+ /api/forward-rules/{id}:
get:
- tags:
- - 邮箱
- summary: 获取邮箱详情
+ tags: [ForwardRules]
+ summary: 规则详情
security:
- cookieAuth: []
parameters:
- - name: id
- in: path
- required: true
- schema:
- type: integer
+ - $ref: '#/components/parameters/ForwardRuleIdParam'
responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Mailbox'
- '404':
- $ref: '#/components/responses/NotFoundError'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
+ '200': { $ref: '#/components/schemas/ForwardRuleResponse' }
+ '404': { $ref: '#/components/responses/NotFoundError' }
+ put:
+ tags: [ForwardRules]
+ summary: 更新规则
+ security:
+ - cookieAuth: []
+ parameters:
+ - $ref: '#/components/parameters/ForwardRuleIdParam'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ForwardRuleInput'
+ responses:
+ '200': { $ref: '#/components/schemas/BasicResponse' }
+ '404': { $ref: '#/components/responses/NotFoundError' }
delete:
- tags:
- - 邮箱
- summary: 删除邮箱
- description: |
- - 普通用户:只能删除自己的邮箱(不能删除默认邮箱)
- - 管理员:可以删除任意邮箱
+ tags: [ForwardRules]
+ summary: 删除规则
security:
- cookieAuth: []
parameters:
- - name: id
- in: path
- required: true
- schema:
- type: integer
+ - $ref: '#/components/parameters/ForwardRuleIdParam'
+ responses:
+ '200': { $ref: '#/components/schemas/BasicResponse' }
+ '404': { $ref: '#/components/responses/NotFoundError' }
+
+ /api/system/health:
+ get:
+ tags: [System]
+ summary: 系统健康检查(公开)
+ description: 返回数据库 / R2 / KV 状态以及 `allow_registration` 只读标志。
responses:
'200':
- description: 删除成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SuccessResponse'
- '400':
- description: 不能删除默认邮箱
+ description: 健康信息
content:
application/json:
schema:
- $ref: '#/components/schemas/ErrorResponse'
- '404':
- $ref: '#/components/responses/NotFoundError'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
+ $ref: '#/components/schemas/SystemHealthResponse'
- /api/mailboxes/applications:
+ /api/system/registration-status:
get:
- tags:
- - 邮箱
- summary: 获取邮箱申请列表
- description: |
- - 普通用户:查看自己的申请记录
- - 管理员:查看所有申请(可分页)
- security:
- - cookieAuth: []
- parameters:
- - $ref: '#/components/parameters/PageParam'
- - $ref: '#/components/parameters/LimitParam'
+ tags: [System]
+ summary: 是否允许注册(只读)
responses:
'200':
- description: 获取成功
+ description: 当前注册策略
content:
application/json:
schema:
- $ref: '#/components/schemas/MailboxApplicationListResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
+ $ref: '#/components/schemas/RegistrationStatusResponse'
- /api/mailboxes/applications/{id}/process:
- post:
- tags:
- - 邮箱
- summary: 处理邮箱申请(仅管理员)
+ /api/system/config:
+ get:
+ tags: [System]
+ summary: 获取系统配置
+ security:
+ - cookieAuth: []
+ responses:
+ '200': { $ref: '#/components/schemas/SystemConfigResponse' }
+ put:
+ tags: [System]
+ summary: 更新系统配置
+ description: 仅管理员可调用;`allow_registration` 为只读,保留未来开关。
security:
- cookieAuth: []
- parameters:
- - name: id
- in: path
- required: true
- schema:
- type: integer
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/ApplicationProcessRequest'
+ $ref: '#/components/schemas/SystemConfigUpdate'
responses:
- '200':
- description: 处理成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SuccessResponse'
- '400':
- description: 请求参数错误
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- '404':
- $ref: '#/components/responses/NotFoundError'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- '403':
- $ref: '#/components/responses/ForbiddenError'
- # ==================== 转发规则 ====================
- /api/forward-rules:
+ '200': { $ref: '#/components/schemas/BasicResponse' }
+
+ /api/database/info:
get:
- tags:
- - 转发规则
- summary: 获取转发规则列表
- description: |
- - 普通用户:仅返回自己的规则,可使用 scope=user 明确指定
- - 管理员:可以返回全部规则(scope=all)或自己的规则(scope=user)
- - 通过 scope 参数控制查询范围
+ tags: [Database]
+ summary: 获取 SQLite 基础信息
+ security:
+ - cookieAuth: []
+ responses:
+ '200': { $ref: '#/components/schemas/DatabaseInfoResponse' }
+
+ /api/database/tables:
+ get:
+ tags: [Database]
+ summary: 查看各表最近数据
security:
- cookieAuth: []
- parameters:
- - $ref: '#/components/parameters/PageParam'
- - $ref: '#/components/parameters/LimitParam'
- - name: scope
- in: query
- description: 查询范围,all=所有规则(需管理员权限),user=当前用户规则,不传则默认返回当前用户规则
- required: false
- schema:
- type: string
- enum: [all, user]
- example: all
- - name: search
- in: query
- schema:
- type: string
- description: 搜索关键词(规则名称、描述)
responses:
'200':
- description: 获取成功
+ description: 各数据表结构 / 最新记录
content:
application/json:
schema:
- $ref: '#/components/schemas/ForwardRuleListResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- '403':
- $ref: '#/components/responses/ForbiddenError'
+ $ref: '#/components/schemas/DatabaseTablesResponse'
+
+ /api/database/stats:
+ get:
+ tags: [Database]
+ summary: 数据库统计
+ security:
+ - cookieAuth: []
+ responses:
+ '200': { $ref: '#/components/schemas/DatabaseStatsResponse' }
+ /api/database/init:
post:
- tags:
- - 转发规则
- summary: 创建转发规则
+ tags: [Database]
+ summary: 初始化数据库(危险操作)
security:
- cookieAuth: []
requestBody:
@@ -870,534 +455,13 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/ForwardRuleCreateRequest'
+ type: object
+ properties:
+ confirmText:
+ type: string
responses:
- '201':
- description: 创建成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ForwardRuleResponse'
- '400':
- description: 请求参数错误
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
- /api/forward-rules/{id}:
- get:
- tags:
- - 转发规则
- summary: 获取转发规则详情
- security:
- - cookieAuth: []
- parameters:
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ForwardRuleResponse'
- '404':
- $ref: '#/components/responses/NotFoundError'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
- put:
- tags:
- - 转发规则
- summary: 更新转发规则
- security:
- - cookieAuth: []
- parameters:
- - name: id
- in: path
- required: true
- schema:
- type: integer
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ForwardRuleUpdateRequest'
- responses:
- '200':
- description: 更新成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SuccessResponse'
- '400':
- description: 请求参数错误
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- '404':
- $ref: '#/components/responses/NotFoundError'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
- delete:
- tags:
- - 转发规则
- summary: 删除转发规则
- security:
- - cookieAuth: []
- parameters:
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: 删除成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SuccessResponse'
- '404':
- $ref: '#/components/responses/NotFoundError'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- # ==================== 邮箱历史 ====================
- /api/mailbox-history/{mailboxId}:
- get:
- tags:
- - 邮箱历史
- summary: 获取指定邮箱的历史记录
- description: 获取指定邮箱的所有操作历史记录
- security:
- - cookieAuth: []
- parameters:
- - name: mailboxId
- in: path
- required: true
- schema:
- type: integer
- description: 邮箱ID
- - $ref: '#/components/parameters/PageParam'
- - $ref: '#/components/parameters/LimitParam'
- responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/MailboxHistoryListResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- '404':
- $ref: '#/components/responses/NotFoundError'
-
- /api/mailbox-history/user/me:
- get:
- tags:
- - 邮箱历史
- summary: 获取当前用户的邮箱历史记录
- description: 获取当前用户所有邮箱的操作历史记录
- security:
- - cookieAuth: []
- parameters:
- - $ref: '#/components/parameters/PageParam'
- - $ref: '#/components/parameters/LimitParam'
- responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UserMailboxHistoryListResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
- /api/mailbox-history/admin/all:
- get:
- tags:
- - 邮箱历史
- summary: 获取所有邮箱历史记录(仅管理员)
- description: 获取系统中所有邮箱的操作历史记录
- security:
- - cookieAuth: []
- parameters:
- - $ref: '#/components/parameters/PageParam'
- - $ref: '#/components/parameters/LimitParam'
- responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UserMailboxHistoryListResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- '403':
- $ref: '#/components/responses/ForbiddenError'
-
- # ==================== 用户信息 ====================
- /api/user-info/username/{userId}:
- get:
- tags:
- - 用户信息
- summary: 根据用户ID获取用户名
- description: 根据用户ID获取用户名和用户类型信息
- security:
- - cookieAuth: []
- parameters:
- - name: userId
- in: path
- required: true
- schema:
- type: integer
- description: 用户ID
- responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- type: object
- properties:
- success:
- type: boolean
- data:
- type: object
- properties:
- id:
- type: integer
- username:
- type: string
- user_type:
- type: string
- '404':
- description: 用户不存在
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
- /api/user-info/usernames:
- post:
- tags:
- - 用户信息
- summary: 批量获取用户名
- description: 根据用户ID列表批量获取用户名信息
- security:
- - cookieAuth: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- properties:
- user_ids:
- type: array
- items:
- type: integer
- description: 用户ID列表
- responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- type: object
- properties:
- success:
- type: boolean
- data:
- type: object
- additionalProperties:
- type: object
- properties:
- id:
- type: integer
- username:
- type: string
- user_type:
- type: string
- '400':
- description: 请求参数错误
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
- # ==================== 系统 ====================
- /api/system/registration-status:
- get:
- tags:
- - 系统
- summary: 获取注册状态
- description: 查询系统是否允许新用户注册
- responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- type: object
- properties:
- success:
- type: boolean
- example: true
- data:
- type: object
- properties:
- allow_registration:
- type: boolean
- description: 是否允许注册
- example: true
- '500':
- $ref: '#/components/responses/InternalServerError'
-
- /api/system/health:
- get:
- tags:
- - 系统
- summary: 获取系统健康状态
- description: 获取无需认证的系统健康状态,包括系统状态、健康检查、配置信息等,使用数字格式
- responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- type: object
- properties:
- success:
- type: boolean
- example: true
- data:
- type: object
- properties:
- health:
- type: object
- properties:
- status:
- type: integer
- description: 系统状态 (1=健康, 0=不健康)
- example: 1
- timestamp:
- type: string
- format: date-time
- description: 检查时间
- example: "2024-01-01T00:00:00.000Z"
- services:
- type: object
- properties:
- database:
- type: object
- properties:
- status:
- type: integer
- description: 数据库状态 (1=正常, 0=异常)
- example: 1
- latency_ms:
- type: integer
- description: 数据库响应延迟(毫秒)
- example: 15
- storage:
- type: object
- properties:
- status:
- type: integer
- description: R2存储状态 (1=正常, 0=异常)
- example: 1
- provider:
- type: integer
- description: 存储提供商 (1=R2, 0=其他)
- example: 1
- kv:
- type: object
- properties:
- status:
- type: integer
- description: KV存储状态 (1=正常, 0=异常)
- example: 1
- provider:
- type: integer
- description: KV提供商 (1=KV, 0=其他)
- example: 1
- total_latency_ms:
- type: integer
- description: 总检查延迟(毫秒)
- example: 25
- config:
- type: object
- properties:
- allow_registration:
- type: integer
- description: 是否允许注册 (1=是, 0=否)
- example: 1
- debug_mode:
- type: integer
- description: 调试模式状态 (1=开启, 0=关闭)
- example: 0
- uptime:
- type: integer
- description: 系统运行时间(秒)
- example: 0
- '500':
- description: 系统不健康
- content:
- application/json:
- schema:
- type: object
- properties:
- success:
- type: boolean
- example: false
- data:
- type: object
- properties:
- health:
- type: object
- properties:
- status:
- type: integer
- example: 0
- timestamp:
- type: string
- format: date-time
- services:
- type: object
- properties:
- database:
- type: object
- properties:
- status:
- type: integer
- example: 0
- latency_ms:
- type: integer
- example: 0
- storage:
- type: object
- properties:
- status:
- type: integer
- example: 0
- provider:
- type: integer
- example: 1
- kv:
- type: object
- properties:
- status:
- type: integer
- example: 0
- provider:
- type: integer
- example: 1
- total_latency_ms:
- type: integer
- example: 0
- error:
- type: string
- example: "系统服务异常"
- config:
- type: object
- properties:
- allow_registration:
- type: integer
- example: 0
- debug_mode:
- type: integer
- example: 0
-
- /api/system/clear-cache:
- post:
- tags:
- - 系统
- summary: 清理系统缓存
- description: 清理系统公开信息缓存(仅管理员)
- security:
- - cookieAuth: []
- responses:
- '200':
- description: 清理成功
- content:
- application/json:
- schema:
- type: object
- properties:
- success:
- type: boolean
- example: true
- message:
- type: string
- example: "系统缓存已清理"
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- '403':
- $ref: '#/components/responses/ForbiddenError'
- '500':
- $ref: '#/components/responses/InternalServerError'
-
- /api/system/config:
- get:
- tags:
- - 系统
- summary: 获取系统配置
- description: 普通用户只能获取公共配置,管理员可以获取完整配置
- security:
- - cookieAuth: []
- responses:
- '200':
- description: 获取成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SystemConfig'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
-
- put:
- tags:
- - 系统
- summary: 更新系统配置(仅管理员)
- security:
- - cookieAuth: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SystemConfigUpdate'
- responses:
- '200':
- description: 更新成功
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SuccessResponse'
- '400':
- description: 请求参数错误
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- '401':
- $ref: '#/components/responses/UnauthorizedError'
- '403':
- $ref: '#/components/responses/ForbiddenError'
+ '200': { $ref: '#/components/schemas/DatabaseInitResponse' }
+ '400': { $ref: '#/components/responses/BadRequest' }
components:
securitySchemes:
@@ -1406,589 +470,369 @@ components:
in: cookie
name: session_cookies
- responses:
- UnauthorizedError:
- description: 未授权
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- examples:
- no_token:
- summary: 未提供认证令牌
- value:
- success: false
- message: "未提供认证令牌"
- invalid_token:
- summary: 无效的认证令牌
- value:
- success: false
- message: "无效的认证令牌"
- expired_token:
- summary: 认证令牌已过期
- value:
- success: false
- message: "认证令牌已过期"
- ForbiddenError:
- description: 权限不足
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- examples:
- admin_required:
- summary: 需要管理员权限
- value:
- success: false
- message: "需要管理员权限"
- insufficient_permissions:
- summary: 权限不足
- value:
- success: false
- message: "权限不足"
- NotFoundError:
- description: 资源不存在
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorResponse'
- examples:
- user_not_found:
- summary: 用户不存在
- value:
- success: false
- message: "用户不存在"
- mailbox_not_found:
- summary: 邮箱不存在
- value:
- success: false
- message: "邮箱不存在"
- email_not_found:
- summary: 邮件不存在
- value:
- success: false
- message: "邮件不存在"
-
parameters:
PageParam:
name: page
in: query
- schema:
- type: integer
- default: 1
- minimum: 1
- description: 页码
+ schema: { type: integer, minimum: 1, default: 1 }
LimitParam:
name: limit
in: query
- schema:
- type: integer
- default: 20
- minimum: 1
- maximum: 100
- description: 每页数量
+ schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
+ EmailIdParam:
+ name: id
+ in: path
+ required: true
+ schema: { type: string }
+ ForwardRuleIdParam:
+ name: id
+ in: path
+ required: true
+ schema: { type: integer }
+
schemas:
- # ==================== 通用 ====================
- SuccessResponse:
+ BasicResponse:
type: object
properties:
- success:
- type: boolean
- example: true
- message:
- type: string
- example: 操作成功
+ success: { type: boolean }
+ message: { type: string }
- ErrorResponse:
+ LoginRequest:
type: object
+ required: [username, password]
properties:
- success:
- type: boolean
- example: false
- message:
- type: string
- example: 错误信息
+ username: { type: string, example: admin }
+ password: { type: string, example: 123456 }
- # ==================== 认证 ====================
LoginResponse:
- type: object
- properties:
- token:
- type: string
- description: JWT token
- message:
- type: string
- example: 登录成功
+ allOf:
+ - $ref: '#/components/schemas/BasicResponse'
+ - type: object
+ properties:
+ data:
+ type: object
+ properties:
+ token: { type: string }
+ user:
+ $ref: '#/components/schemas/UserProfile'
+
+ UserProfileResponse:
+ allOf:
+ - $ref: '#/components/schemas/BasicResponse'
+ - type: object
+ properties:
+ data:
+ $ref: '#/components/schemas/UserProfile'
- # ==================== 用户 ====================
UserProfile:
type: object
properties:
- id:
- type: integer
- username:
- type: string
- email:
- type: string
- format: email
- user_type:
- type: string
- enum: [user, admin]
- status:
- type: integer
- enum: [1, 2, 3]
- description: 用户状态:1=激活, 2=停用, 3=删除
- created_at:
- type: string
- format: date-time
- updated_at:
- type: string
- format: date-time
+ id: { type: integer }
+ username: { type: string }
+ email: { type: string, format: email }
+ user_type: { type: integer, enum: [1], description: 固定为管理员 }
+ created_at: { type: string }
+ updated_at: { type: string }
settings:
- $ref: '#/components/schemas/UserSettings'
-
- UserSettings:
- type: object
- properties:
- webhook_url:
- type: string
- format: uri
- nullable: true
- webhook_secret:
- type: string
- nullable: true
+ type: object
+ properties:
+ webhook_url: { type: string, nullable: true }
+ webhook_secret: { type: string, nullable: true }
UserSettingsUpdate:
type: object
properties:
- password:
- type: string
- webhook_url:
- type: string
- format: uri
- webhook_secret:
- type: string
-
- UserListResponse:
- type: object
- properties:
- total:
- type: integer
- items:
- type: array
- items:
- $ref: '#/components/schemas/UserProfile'
+ password: { type: string, minLength: 6 }
+ webhook_url: { type: string }
+ webhook_secret: { type: string }
- UserResponse:
- type: object
- properties:
- user:
- $ref: '#/components/schemas/UserProfile'
-
- AdminUserCreateRequest:
- type: object
- required:
- - username
- - password
- - email
- properties:
- username:
- type: string
- password:
- type: string
- email:
- type: string
- format: email
- user_type:
- type: string
- enum: [user, admin]
- default: user
-
- # ==================== 邮件 ====================
EmailListResponse:
- type: object
- properties:
- total:
- type: integer
- items:
- type: array
- items:
- $ref: '#/components/schemas/EmailSummary'
+ allOf:
+ - $ref: '#/components/schemas/BasicResponse'
+ - type: object
+ properties:
+ data:
+ type: object
+ properties:
+ total: { type: integer }
+ items:
+ type: array
+ items: { $ref: '#/components/schemas/EmailSummary' }
EmailSummary:
type: object
properties:
- id:
- type: string
- subject:
- type: string
- from:
- type: string
- to:
- type: string
- status:
- type: string
- enum: [received, processed, forwarded, failed]
- received_at:
- type: string
- format: date-time
-
- EmailDetail:
+ id: { type: string }
+ subject: { type: string }
+ sender_email: { type: string }
+ recipient_email: { type: string }
+ received_at: { type: string }
+ has_attachments: { type: integer, enum: [0,1] }
+
+ EmailDetailResponse:
allOf:
- - $ref: '#/components/schemas/EmailSummary'
+ - $ref: '#/components/schemas/BasicResponse'
- type: object
properties:
- content:
- type: string
- attachments:
- type: array
- items:
- $ref: '#/components/schemas/Attachment'
-
- Attachment:
- type: object
- properties:
- id:
- type: string
- filename:
- type: string
- size:
- type: integer
+ data:
+ type: object
+ properties:
+ id: { type: string }
+ subject: { type: string }
+ content: { type: string }
+ content_type: { type: string }
+ attachments:
+ type: array
+ items:
+ type: object
+ properties:
+ id: { type: integer }
+ filename: { type: string }
+ size_bytes: { type: integer }
- # ==================== 邮箱 ====================
- Mailbox:
+ SendEmailRequest:
type: object
+ required: [to, subject, content]
properties:
- id:
- type: integer
- owner_id:
- type: integer
- owner_username:
- type: string
- description: 邮箱所有者用户名
- owner_usertype:
- type: integer
- enum: [0, 1]
- description: 邮箱所有者用户类型(0: 普通用户, 1: 管理员)
- address:
- type: string
- status:
- type: integer
- enum: [1, 2, 3]
- description: 邮箱状态(1: 正常, 2: 停用(管理员封禁), 3: 已删除(用户删除)
- deleted_at:
+ to: { type: string }
+ from: { type: string }
+ subject: { type: string }
+ content: { type: string }
+ content_type:
type: string
- format: date-time
- nullable: true
- description: 删除时间
- created_at:
- type: string
- format: date-time
- updated_at:
- type: string
- format: date-time
+ enum: [text, html, markdown]
- MailboxResponse:
+ ForwardRuleInput:
type: object
+ required: [rule_name]
properties:
- mailbox:
- $ref: '#/components/schemas/Mailbox'
-
- MailboxListResponse:
- type: object
- properties:
- total:
- type: integer
- items:
+ rule_name: { type: string }
+ sender_filter: { type: string }
+ keyword_filter: { type: string }
+ recipient_filter: { type: string }
+ enabled: { type: integer, enum: [0, 1] }
+ webhooks:
type: array
+ description: 规则关联的多个 webhook 配置
items:
- $ref: '#/components/schemas/Mailbox'
+ $ref: '#/components/schemas/ForwardRuleWebhook'
- MailboxApplicationRequest:
- type: object
- properties:
- reason:
- type: string
- description: 申请理由
+ ForwardRuleResponse:
+ allOf:
+ - $ref: '#/components/schemas/BasicResponse'
+ - type: object
+ properties:
+ data:
+ type: object
+ properties:
+ rule: { $ref: '#/components/schemas/ForwardRule' }
- MailboxApplicationResponse:
- type: object
- properties:
- application_id:
- type: integer
- status:
- type: integer
- enum: [0, 1, 2]
- description: "0=待审核, 1=已批准, 2=已拒绝"
+ ForwardRuleListResponse:
+ allOf:
+ - $ref: '#/components/schemas/BasicResponse'
+ - type: object
+ properties:
+ data:
+ type: object
+ properties:
+ total: { type: integer }
+ items:
+ type: array
+ items: { $ref: '#/components/schemas/ForwardRule' }
- MailboxApplicationListResponse:
+ ForwardRule:
type: object
properties:
- total:
- type: integer
- items:
+ id: { type: integer }
+ rule_name: { type: string }
+ sender_filter: { type: string }
+ keyword_filter: { type: string }
+ recipient_filter: { type: string }
+ webhook_url: { type: string, description: 保留字段以兼容旧数据 }
+ webhook_secret: { type: string, description: 保留字段以兼容旧数据 }
+ webhook_type: { type: string, enum: [dingtalk, feishu, bark, custom], description: 保留字段以兼容旧数据 }
+ enabled: { type: integer, enum: [0, 1] }
+ created_at: { type: string }
+ updated_at: { type: string }
+ webhooks:
type: array
+ description: 规则关联的多个 webhook 配置
items:
- $ref: '#/components/schemas/MailboxApplicationResponse'
-
- ApplicationProcessRequest:
- type: object
- required:
- - action
- properties:
- action:
- type: integer
- enum: [1, 2]
- description: "1=批准, 2=拒绝"
-
- AdminMailboxCreateRequest:
- type: object
- required:
- - address
- - owner_id
- properties:
- address:
- type: string
- owner_id:
- type: integer
+ $ref: '#/components/schemas/ForwardRuleWebhook'
- # ==================== 转发规则 ====================
- ForwardRuleBase:
+ ForwardRuleWebhook:
type: object
properties:
- rule_name:
- type: string
- description: 规则名称
- description:
- type: string
- nullable: true
- description: 规则描述
- source_email:
- type: string
- description: 源邮箱地址
- target_email:
- type: string
- description: 目标邮箱地址
- sender_filter:
- type: string
- nullable: true
- description: 发件人过滤条件
- keyword_filter:
- type: string
- nullable: true
- description: 关键词过滤条件
- recipient_filter:
- type: string
- nullable: true
- description: 收件人过滤条件
- webhook_url:
- type: string
- format: uri
- description: Webhook URL
- webhook_secret:
- type: string
- nullable: true
- description: Webhook 密钥
- webhook_type:
- type: string
- enum: [dingtalk, feishu, custom]
- description: Webhook 类型
- enabled:
- type: boolean
- description: 是否启用
-
- ForwardRuleCreateRequest:
+ id: { type: integer }
+ rule_id: { type: integer }
+ webhook_url: { type: string }
+ webhook_secret: { type: string }
+ webhook_type: { type: string, enum: [dingtalk, feishu, bark, custom] }
+ enabled: { type: integer, enum: [0, 1] }
+ created_at: { type: string }
+ updated_at: { type: string }
+
+ SystemHealthResponse:
allOf:
- - $ref: '#/components/schemas/ForwardRuleBase'
+ - $ref: '#/components/schemas/BasicResponse'
- type: object
- required:
- - rule_name
- - source_email
- - target_email
- - webhook_url
+ properties:
+ data:
+ type: object
+ properties:
+ health:
+ type: object
+ properties:
+ status: { type: integer }
+ timestamp: { type: string }
+ services:
+ type: object
+ properties:
+ database:
+ type: object
+ properties:
+ status: { type: integer }
+ latency_ms: { type: integer }
+ storage:
+ type: object
+ properties:
+ status: { type: integer }
+ provider: { type: integer }
+ kv:
+ type: object
+ properties:
+ status: { type: integer }
+ provider: { type: integer }
+ config:
+ type: object
+ properties:
+ allow_registration:
+ type: integer
+ description: 只读标志,当前固定为禁止注册
+ debug_mode: { type: integer }
- ForwardRuleUpdateRequest:
+ RegistrationStatusResponse:
allOf:
- - $ref: '#/components/schemas/ForwardRuleBase'
+ - $ref: '#/components/schemas/BasicResponse'
+ - type: object
+ properties:
+ data:
+ type: object
+ properties:
+ allow_registration: { type: integer }
- ForwardRuleResponse:
- type: object
- properties:
- id:
- type: integer
- description: 规则ID
- name:
- type: string
- description: 规则名称(rule_name的别名)
- description:
- type: string
- nullable: true
- description: 规则描述
- source_email:
- type: string
- description: 源邮箱地址
- target_email:
- type: string
- description: 目标邮箱地址
- enabled:
- type: boolean
- description: 是否启用
- created_at:
- type: string
- format: date-time
- description: 创建时间
- updated_at:
- type: string
- format: date-time
- description: 更新时间
- user_id:
- type: integer
- description: 用户ID(仅管理员可见)
- username:
- type: string
- description: 用户名(仅管理员可见)
+ SystemConfigResponse:
+ allOf:
+ - $ref: '#/components/schemas/BasicResponse'
+ - type: object
+ properties:
+ data:
+ type: object
+ properties:
+ config:
+ type: object
+ additionalProperties: true
+ user_role:
+ type: string
+ enum: ['admin']
- ForwardRuleListResponse:
+ SystemConfigUpdate:
type: object
properties:
- total:
- type: integer
- items:
+ debug_mode: { type: integer, enum: [0,1] }
+ mail_retention_days: { type: integer, description: 邮件保留天数 }
+ attachment_retention_days: { type: integer, description: 附件保留天数 }
+ max_attachment_size: { type: integer, description: 单位:字节 }
+ cookie_max_age: { type: integer, description: 单位:秒 }
+ primary_domain: { type: string }
+ admin_email: { type: string }
+ domains:
type: array
- items:
- $ref: '#/components/schemas/ForwardRuleResponse'
+ items: { type: string }
+ default_webhook_url: { type: string, description: 默认 Webhook URL(系统级别,所有邮件都会发送到此 webhook,如果为空则不通知) }
+ default_webhook_secret: { type: string, description: 默认 Webhook 密钥(可选) }
+ default_webhook_type: { type: string, enum: [dingtalk, feishu, bark, custom], description: 默认 Webhook 类型 }
- # ==================== 邮箱历史 ====================
- MailboxHistory:
- type: object
- properties:
- id:
- type: integer
- mailbox_id:
- type: integer
- user_id:
- type: integer
- description: 操作人用户ID
- owner_id:
- type: integer
- description: 邮箱所有者ID
- action_type:
- type: integer
- enum: [1, 2, 3]
- description: 操作类型(1=创建, 2=删除, 3=停用)
- created_at:
- type: string
- format: date-time
- user_username:
- type: string
- description: 操作人用户名
- owner_username:
- type: string
- description: 邮箱所有者用户名
- mailbox_address:
- type: string
- description: 邮箱地址(仅在用户历史记录中包含)
-
- MailboxHistoryListResponse:
- type: object
- properties:
- total:
- type: integer
- items:
- type: array
- items:
- $ref: '#/components/schemas/MailboxHistory'
+ DatabaseInfoResponse:
+ allOf:
+ - $ref: '#/components/schemas/BasicResponse'
+ - type: object
+ properties:
+ data:
+ type: object
+ properties:
+ sqliteVersion: { type: string }
+ tables:
+ type: object
+ additionalProperties:
+ type: object
+ properties:
+ recordCount: { type: integer }
+ error: { type: string }
- UserMailboxHistoryListResponse:
- type: object
- properties:
- total:
- type: integer
- items:
- type: array
- items:
- $ref: '#/components/schemas/MailboxHistory'
+ DatabaseTablesResponse:
+ allOf:
+ - $ref: '#/components/schemas/BasicResponse'
+ - type: object
+ properties:
+ data:
+ type: array
+ items:
+ type: object
+ properties:
+ table: { type: string }
+ columns:
+ type: array
+ items: { type: string }
+ records:
+ type: array
+ items: { type: object }
+ total: { type: integer }
+ error: { type: string }
+
+ DatabaseStatsResponse:
+ allOf:
+ - $ref: '#/components/schemas/BasicResponse'
+ - type: object
+ properties:
+ data:
+ type: object
+ additionalProperties: true
- # ==================== 系统 ====================
- SystemConfig:
- type: object
- properties:
- debug_mode:
- type: integer
- description: 调试模式状态 (1=开启, 0=关闭)
- default: 0
- allow_registration:
- type: integer
- description: 是否允许新用户注册 (1=是, 0=否)
- default: 1
- auto_approve_mailbox:
- type: integer
- description: 是否自动审核邮箱申请 (1=是, 0=否)
- default: 0
- supported_domains:
- type: array
- items:
- type: string
- description: 支持的邮箱域名列表
- example: ["example.com", "test.com"]
- mail_retention_days:
- type: integer
- minimum: 0
- maximum: 365
- description: 邮件保留天数,0表示永不过期
- default: 30
- attachment_max_size:
- type: integer
- minimum: 1
- maximum: 100
- description: 单个附件最大大小(MB)
- default: 50
- allow_user_send:
- type: integer
- description: 是否允许普通用户发送邮件 (1=是, 0=否)(预留接口,暂未实现)
- default: 1
- max_mailboxes_per_user:
- type: integer
- minimum: 1
- description: 每个用户最大邮箱数量(预留接口,暂未实现)
- default: 5
- storage_provider:
- type: string
- enum: [r2, s3, local]
- description: 存储提供商(预留接口,暂未实现,默认使用 R2)
+ DatabaseInitResponse:
+ allOf:
+ - $ref: '#/components/schemas/BasicResponse'
+ - type: object
+ properties:
+ data:
+ type: object
+ properties:
+ steps:
+ type: array
+ items: { type: string }
- SystemConfigUpdate:
- type: object
- properties:
- debug_mode:
- type: integer
- description: 调试模式状态 (1=开启, 0=关闭)
- allow_registration:
- type: integer
- description: 是否允许新用户注册 (1=是, 0=否)
- auto_approve_mailbox:
- type: integer
- description: 是否自动审核邮箱申请 (1=是, 0=否)
- supported_domains:
- type: array
- items:
- type: string
- mail_retention_days:
- type: integer
- minimum: 0
- maximum: 365
- attachment_max_size:
- type: integer
- minimum: 1
- maximum: 100
- allow_user_send:
- type: integer
- description: 是否允许普通用户发送邮件 (1=是, 0=否)(预留接口,暂未实现)
- max_mailboxes_per_user:
- type: integer
- minimum: 1
- description: 每个用户最大邮箱数量(预留接口,暂未实现)
- storage_provider:
- type: string
- enum: [r2, s3, local]
- description: 存储提供商(预留接口,暂未实现,默认使用 R2)
\ No newline at end of file
+ responses:
+ UnauthorizedError:
+ description: 未登录或会话失效
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/BasicResponse'
+ ForbiddenError:
+ description: 权限不足
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/BasicResponse'
+ BadRequest:
+ description: 请求参数不合法
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/BasicResponse'
+ NotFoundError:
+ description: 资源不存在
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/BasicResponse'
diff --git a/cloudflare_temp_email/.flake8 b/cloudflare_temp_email/.flake8
deleted file mode 100644
index b6cd654..0000000
--- a/cloudflare_temp_email/.flake8
+++ /dev/null
@@ -1,3 +0,0 @@
-[flake8]
-max-line-length = 180
-exclude = .git,__pycache__,build,dist
diff --git "a/cloudflare_temp_email/.github/ISSUE_TEMPLATE/bug-\345\217\215\351\246\210.md" "b/cloudflare_temp_email/.github/ISSUE_TEMPLATE/bug-\345\217\215\351\246\210.md"
deleted file mode 100644
index 4678fed..0000000
--- "a/cloudflare_temp_email/.github/ISSUE_TEMPLATE/bug-\345\217\215\351\246\210.md"
+++ /dev/null
@@ -1,23 +0,0 @@
----
-name: Bug 反馈
-about: Create a report to help us improve
-title: "[BUG]"
-labels: bug
-assignees: ''
-
----
-
-## 复现步骤
-
-
-
-## 预期行为
-
-
-
-## 部署方式
-
-- [ ] cli 部署
-- [ ] 用户界面部署
-
-## 浏览器环境
diff --git a/cloudflare_temp_email/.github/ISSUE_TEMPLATE/feature-request.md b/cloudflare_temp_email/.github/ISSUE_TEMPLATE/feature-request.md
deleted file mode 100644
index d99141e..0000000
--- a/cloudflare_temp_email/.github/ISSUE_TEMPLATE/feature-request.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-name: Feature Request
-about: Suggest an idea for this project
-title: "[Feature]"
-labels: enhancement, good first issue
-assignees: ''
-
----
-
-## 请描述您的 Feature
-
-## 描述您想要的解决方案
-
-## 描述您考虑过的替代方案
-
-## 附加上下文
diff --git a/cloudflare_temp_email/.github/config/mail-parser-wasm-worker.patch b/cloudflare_temp_email/.github/config/mail-parser-wasm-worker.patch
deleted file mode 100644
index 289b9a8..0000000
--- a/cloudflare_temp_email/.github/config/mail-parser-wasm-worker.patch
+++ /dev/null
@@ -1,44 +0,0 @@
-diff --git a/worker/src/common.ts b/worker/src/common.ts
-index bd9bcc9..e7e2748 100644
---- a/worker/src/common.ts
-+++ b/worker/src/common.ts
-@@ -273,23 +273,23 @@ export const commonParseMail = async (parsedEmailContext: ParsedEmailContext): P
- }
- const raw_mail = parsedEmailContext.rawEmail;
- // TODO: WASM parse email
-- // try {
-- // const { parse_message_wrapper } = await import('mail-parser-wasm-worker');
-+ try {
-+ const { parse_message_wrapper } = await import('mail-parser-wasm-worker');
-
-- // const parsedEmail = parse_message_wrapper(raw_mail);
-- // parsedEmailContext.parsedEmail = {
-- // sender: parsedEmail.sender || "",
-- // subject: parsedEmail.subject || "",
-- // text: parsedEmail.text || "",
-- // headers: parsedEmail.headers?.map(
-- // (header) => ({ key: header.key, value: header.value })
-- // ) || [],
-- // html: parsedEmail.body_html || "",
-- // };
-- // return parsedEmailContext.parsedEmail;
-- // } catch (e) {
-- // console.error("Failed use mail-parser-wasm-worker to parse email", e);
-- // }
-+ const parsedEmail = parse_message_wrapper(raw_mail);
-+ parsedEmailContext.parsedEmail = {
-+ sender: parsedEmail.sender || "",
-+ subject: parsedEmail.subject || "",
-+ text: parsedEmail.text || "",
-+ headers: parsedEmail.headers?.map(
-+ (header) => ({ key: header.key, value: header.value })
-+ ) || [],
-+ html: parsedEmail.body_html || "",
-+ };
-+ return parsedEmailContext.parsedEmail;
-+ } catch (e) {
-+ console.error("Failed use mail-parser-wasm-worker to parse email", e);
-+ }
- try {
- const { default: PostalMime } = await import('postal-mime');
- const parsedEmail = await PostalMime.parse(raw_mail);
diff --git a/cloudflare_temp_email/.github/workflows/docs_deploy.yml b/cloudflare_temp_email/.github/workflows/docs_deploy.yml
deleted file mode 100644
index 7739b1c..0000000
--- a/cloudflare_temp_email/.github/workflows/docs_deploy.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-name: Deploy Docs
-
-on:
- push:
- paths:
- - "vitepress-docs/**"
- tags:
- - "*"
- workflow_dispatch:
-
-jobs:
- deploy:
- runs-on: ubuntu-latest
- permissions:
- contents: write
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 20
-
- - uses: pnpm/action-setup@v3
- name: Install pnpm
- id: pnpm-install
- with:
- version: 8
- run_install: false
-
- - name: check github release done
- run: |
- for ((attempt=1; attempt<=10; attempt++)); do
- if wget -q --spider "https://github.com/dreamhunter2333/cloudflare_temp_email/releases/latest/download/frontend.zip"; then
- echo "frontend.zip found."
- break
- else
- if [ $attempt -eq 10 ]; then
- echo "Exceeded maximum retries. frontend.zip not found."
- else
- echo "frontend.zip not found. Retrying in 30 seconds..."
- sleep 30
- fi
- fi
- done
-
- - name: Deploy Docs for ${{github.ref_name}}
- run: |
- cd vitepress-docs/
- wget https://github.com/dreamhunter2333/cloudflare_temp_email/releases/latest/download/frontend.zip -O docs/public/ui_install/frontend.zip
- pnpm install --no-frozen-lockfile
- if [[ ${{github.ref}} == refs/tags/* ]]; then
- export TAG_NAME=${{github.ref_name}}
- else
- export TAG_NAME=$(git describe --tags --abbrev=0)
- fi
- echo "Deploying docs for tag $TAG_NAME"
- pnpm run deploy
- env:
- CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
diff --git a/cloudflare_temp_email/.github/workflows/frontend_deploy.yaml b/cloudflare_temp_email/.github/workflows/frontend_deploy.yaml
deleted file mode 100644
index ba8605d..0000000
--- a/cloudflare_temp_email/.github/workflows/frontend_deploy.yaml
+++ /dev/null
@@ -1,64 +0,0 @@
-name: Deploy Frontend
-
-on:
- workflow_run:
- workflows: [Upstream Sync]
- types: [completed]
- push:
- tags:
- - "*"
- workflow_dispatch:
-
-jobs:
- deploy:
- runs-on: ubuntu-latest
- permissions:
- contents: write
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 20
-
- - uses: pnpm/action-setup@v3
- name: Install pnpm
- id: pnpm-install
- with:
- version: 8
- run_install: false
-
- - name: Deploy Frontend for ${{ github.ref_name }}
- run: |
- cd frontend/
- echo "${{ secrets.FRONTEND_ENV }}" > .env.prod
- export project_name=${{ secrets.FRONTEND_NAME }}
- pnpm install --no-frozen-lockfile
- export frontend_branch=${{ secrets.FRONTEND_BRANCH }}
- if [ -n "$frontend_branch" ]; then
- echo "Deploying branch $frontend_branch"
- pnpm run deploy:actions --project-name=$project_name --branch $frontend_branch
- else
- echo "Deploying branch production"
- pnpm run deploy --project-name=$project_name
- fi
- echo "Deploying production for ${{ github.ref_name }}"
- echo "Deployed for tag ${{ github.ref_name }}"
-
- export tg_mini_app_project_name=${{ secrets.TG_FRONTEND_NAME }}
- if [ -n "$tg_mini_app_project_name" ]; then
- echo "Deploying telegram mini app $tg_mini_app_project_name"
- if [ -n "$frontend_branch" ]; then
- echo "Deploying telegram mini app branch $frontend_branch"
- pnpm run deploy:actions:telegram --project-name=$tg_mini_app_project_name --branch $frontend_branch
- else
- echo "Deploying telegram mini app branch production"
- pnpm run deploy:telegram --project-name=$tg_mini_app_project_name
- fi
- echo "Deployed telegram mini app for ${{ github.ref_name }}"
- fi
- env:
- CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
diff --git a/cloudflare_temp_email/.github/workflows/frontend_pagefunction_deploy.yaml b/cloudflare_temp_email/.github/workflows/frontend_pagefunction_deploy.yaml
deleted file mode 100644
index e5b2960..0000000
--- a/cloudflare_temp_email/.github/workflows/frontend_pagefunction_deploy.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-name: Deploy Frontend with page function
-
-on:
- workflow_dispatch:
-
-jobs:
- deploy:
- runs-on: ubuntu-latest
- permissions:
- contents: write
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 20
-
- - uses: pnpm/action-setup@v3
- name: Install pnpm
- id: pnpm-install
- with:
- version: 8
- run_install: false
-
- - name: Deploy Frontend for ${{ github.ref_name }}
- run: |
- cd frontend/
- pnpm install --no-frozen-lockfile
- pnpm build:pages
- cd ../pages/
- echo '${{ secrets.PAGE_TOML }}' > wrangler.toml
- pnpm install --no-frozen-lockfile
- pnpm run deploy
- echo "Deploying prodcution for ${{ github.ref_name }}"
- env:
- CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
diff --git a/cloudflare_temp_email/.github/workflows/pr_agent.yml b/cloudflare_temp_email/.github/workflows/pr_agent.yml
deleted file mode 100644
index 7cd371d..0000000
--- a/cloudflare_temp_email/.github/workflows/pr_agent.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Codium PR Agent
-
-on:
- pull_request:
- types: [opened, reopened, ready_for_review]
-jobs:
- pr_agent_job:
- if: ${{ github.event.sender.type != 'Bot' }}
- runs-on: ubuntu-latest
- permissions:
- issues: write
- pull-requests: write
- name: Run pr agent on every pull request, respond to user comments
- steps:
- - name: PR Agent action step
- id: pragent
- uses: docker://codiumai/pr-agent:0.29-github_action
- env:
- PR_REVIEWER.REQUIRE_TESTS_REVIEW: "false"
- OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
- OPENAI_API_BASE: ${{ secrets.OPENAI_API_BASE }}
- CONFIG.MODEL: "gpt-4o"
- CONFIG.MODEL_TURBO: "gpt-4o"
- OPENAI.API_BASE: ${{ secrets.OPENAI_API_BASE }}
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/cloudflare_temp_email/.github/workflows/smtp_proxy_server.yml b/cloudflare_temp_email/.github/workflows/smtp_proxy_server.yml
deleted file mode 100644
index 1a2d7f6..0000000
--- a/cloudflare_temp_email/.github/workflows/smtp_proxy_server.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-name: SMTP Proxy Server Docker Image CI
-
-on:
- push:
- paths:
- - "smtp_proxy_server/**"
- tags:
- - "*"
- branches:
- - main
- workflow_dispatch:
-
-env:
- REGISTRY: ghcr.io
- IMAGE_NAME: smtp_proxy_server
-
-jobs:
- build-and-push-image:
- runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
- steps:
- - uses: actions/checkout@v4
-
- - name: Log in to the Container registry
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
-
- - name: Build and push Docker images
- uses: docker/build-push-action@v5
- with:
- context: ./smtp_proxy_server
- file: ./smtp_proxy_server/dockerfile
- platforms: linux/amd64,linux/arm64
- push: true
- tags: |
- ${{ env.REGISTRY }}/${{ github.repository }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
- ${{ env.REGISTRY }}/${{ github.repository }}/${{ env.IMAGE_NAME }}:latest
diff --git a/cloudflare_temp_email/.github/workflows/sync.yaml b/cloudflare_temp_email/.github/workflows/sync.yaml
deleted file mode 100644
index 0ab1407..0000000
--- a/cloudflare_temp_email/.github/workflows/sync.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Upstream Sync
-
-on:
- schedule:
- - cron: "0 0 * * 1"
- workflow_dispatch:
-
-jobs:
- sync_latest_from_upstream:
- name: Sync latest commits from upstream repo
- runs-on: ubuntu-latest
- if: ${{ github.event.repository.fork }}
-
- steps:
- - uses: actions/checkout@v4
-
- - name: Sync upstream changes
- id: sync
- uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
- with:
- upstream_sync_repo: dreamhunter2333/cloudflare_temp_email
- upstream_sync_branch: main
- target_sync_branch: main
- target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
- test_mode: false
diff --git a/cloudflare_temp_email/.github/workflows/tag_build.yml b/cloudflare_temp_email/.github/workflows/tag_build.yml
deleted file mode 100644
index 3032706..0000000
--- a/cloudflare_temp_email/.github/workflows/tag_build.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-name: Tag Build CI
-
-on:
- push:
- tags:
- - "*"
-
-jobs:
- build:
- runs-on: ubuntu-latest
- permissions:
- contents: write
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 20
-
- - uses: pnpm/action-setup@v3
- name: Install pnpm
- id: pnpm-install
- with:
- version: 8
- run_install: false
-
- - name: Build Frontend
- run: cd frontend && pnpm install --no-frozen-lockfile && pnpm build:release
-
- - name: Zip Frontend dist
- run: cd frontend/dist/ && zip -r frontend.zip * && mv frontend.zip ../
-
- - name: Build Telegram Frontend
- run: cd frontend && pnpm install --no-frozen-lockfile && pnpm build:telegram:release
-
- - name: Zip Telegram Frontend dist
- run: cd frontend/dist/ && zip -r telegram-frontend.zip * && mv telegram-frontend.zip ../
-
- - name: cp wrangler.toml
- run: cd worker && cp wrangler.toml.template wrangler.toml
-
- - name: Build Backend
- run: cd worker && pnpm install --no-frozen-lockfile && pnpm build
-
- - name: Move worker.js
- run: cd worker/dist && mv worker.js ../
-
- - name: Build Worker with wasm mail parser
- run: |
- cd worker
- echo "Using mail-parser-wasm-worker"
- pnpm add mail-parser-wasm-worker
- git apply ../.github/config/mail-parser-wasm-worker.patch
- echo "Applied mail-parser-wasm-worker patch"
- pnpm build
- zip -r worker-with-wasm-mail-parser.zip dist/worker.js dist/*.wasm
-
- - name: Upload to Release
- uses: softprops/action-gh-release@v2
- with:
- files: |
- frontend/frontend.zip
- frontend/telegram-frontend.zip
- worker/worker.js
- worker/worker-with-wasm-mail-parser.zip
diff --git a/cloudflare_temp_email/.gitignore b/cloudflare_temp_email/.gitignore
deleted file mode 100644
index 42fc586..0000000
--- a/cloudflare_temp_email/.gitignore
+++ /dev/null
@@ -1,139 +0,0 @@
-.DS_Store
-dist/
-test/
-.vscode/
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-lerna-debug.log*
-.pnpm-debug.log*
-
-# Diagnostic reports (https://nodejs.org/api/report.html)
-report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
-
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-*.lcov
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# Bower dependency directory (https://bower.io/)
-bower_components
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (https://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directories
-node_modules/
-jspm_packages/
-
-# Snowpack dependency directory (https://snowpack.dev/)
-web_modules/
-
-# TypeScript cache
-*.tsbuildinfo
-
-# Optional npm cache directory
-.npm
-
-# Optional eslint cache
-.eslintcache
-
-# Optional stylelint cache
-.stylelintcache
-
-# Microbundle cache
-.rpt2_cache/
-.rts2_cache_cjs/
-.rts2_cache_es/
-.rts2_cache_umd/
-
-# Optional REPL history
-.node_repl_history
-
-# Output of 'npm pack'
-*.tgz
-
-# Yarn Integrity file
-.yarn-integrity
-
-# dotenv environment variable files
-.env
-.env.development.local
-.env.test.local
-.env.production.local
-.env.local
-
-# parcel-bundler cache (https://parceljs.org/)
-.cache
-.parcel-cache
-
-# Next.js build output
-.next
-out
-
-# Nuxt.js build / generate output
-.nuxt
-dist
-
-# Gatsby files
-.cache/
-# Comment in the public line in if your project uses Gatsby and not Next.js
-# https://nextjs.org/blog/next-9-1#public-directory-support
-# public
-
-# vuepress build output
-.vuepress/dist
-
-# vuepress v2.x temp and cache directory
-.temp
-.cache
-
-# Docusaurus cache and generated files
-.docusaurus
-
-# Serverless directories
-.serverless/
-
-# FuseBox cache
-.fusebox/
-
-# DynamoDB Local files
-.dynamodb/
-
-# TernJS port file
-.tern-port
-
-# Stores VSCode versions used for testing VSCode extensions
-.vscode-test
-
-# yarn v2
-.yarn/cache
-.yarn/unplugged
-.yarn/build-state.yml
-.yarn/install-state.gz
-.pnp.*
-
-.wrangler
-wrangler.toml
-.dev.vars
-pnpm-lock.yaml
diff --git a/cloudflare_temp_email/CHANGELOG.md b/cloudflare_temp_email/CHANGELOG.md
deleted file mode 100644
index e501f39..0000000
--- a/cloudflare_temp_email/CHANGELOG.md
+++ /dev/null
@@ -1,535 +0,0 @@
-
-# CHANGE LOG
-
-## main(v1.0.5)
-
-- feat: 新增 `DISABLE_CUSTOM_ADDRESS_NAME` 配置: 禁用自定义邮箱地址名称功能
-- feat: 新增 `CREATE_ADDRESS_DEFAULT_DOMAIN_FIRST` 配置: 创建地址时优先使用第一个域名
-- feat: |UI| 主页增加进入极简模式按钮
-- feat: |Webhook| 增加白名单开关功能,支持灵活控制访问权限
-
-## v1.0.4
-
-- feat: |UI| 优化极简模式主页, 增加全部邮件页面功能(删除/下载/附件/...), 可在 `外观` 中切换
-- feat: admin 账号设置页面增加 `邮件转发规则` 配置
-- feat: admin 账号设置页面增加 `禁止接收未知地址邮件` 配置
-- feat: 邮件页面增加 上一封/下一封 按钮
-
-## v1.0.3
-
-- fix: 修复 github actions 部署问题
-- feat: telegram /new 不指定域名时, 使用随机地址
-
-## v1.0.2
-
-- fix: 修复 oauth2 登录失败的问题
-
-## v1.0.1
-
-- feat: |UI| 增加极简模式主页, 可在 `外观` 中切换
-- fix: 修复 oauth2 登录时,default role 不生效的问题
-
-## v1.0.0
-
-- fix: |UI| 修复 User 查看收件箱,不选择地址时,关键词查询不生效
-- fix: 修复自动清理任务,时间为 0 时不生效的问题
-- feat: 清理功能增加 创建 n 天前地址清理,n 天前未活跃地址清理
-- fix: |IMAP Proxy| 修复 IMAP Proxy 服务器,无法查看新邮件的问题
-
-## v0.10.0
-
-- feat: 支持 User 查看收件箱,`/user_api/mails` 接口, 支持 `address` 和 `keyword` 过滤
-- fix: 修复 Oauth2 登录获取 Token 时,一些 Oauth2 需要 `redirect_uri` 参数的问题
-- feat: 用户访问网页时,如果 `user token` 在 7 天内过期,自动刷新
-- feat: admin portal 中增加初始化 db 的功能
-- feat: 增加 `ALWAYS_SHOW_ANNOUNCEMENT` 变量,用于配置是否总是显示公告
-
-## v0.9.1
-
-- feat: |UI| support google ads
-- feat: |UI| 使用 shadow DOM 防止样式污染
-- feat: |UI| 支持 URL jwt 参数自动登录邮箱,jwt 参数会覆盖浏览器中的 jwt
-- fix: |CleanUP| 修复清理邮件时,清理时间超过 30 天报错的 bug
-- feat: admin 用户管理页面: 增加 用户地址查看功能
-- feat: | S3 附件| 增加 S3 附件删除功能
-- feat: | Admin API| 增加 admin 绑定用户和地址的 api
-- feat: | Oauth2 | Oatuh2 获取用户信息时,支持 `JSONPATH` 表达式
-
-## v0.9.0
-
-- feat: | Worker | 支持多语言
-- feat: | Worker | `NO_LIMIT_SEND_ROLE` 配置支持多角色, 逗号分割
-- feat: | Actions | build 里增加 `worker-with-wasm-mail-parser.zip` 支持 UI 部署带 `wasm` 的 worker
-
-## v0.8.7
-
-- fix: |UI| 修复移动设备日期显示问题
-- feat: |Worker| 支持通过 `SMTP` 发送邮件, 使用 [zou-yu/worker-mailer](https://github.com/zou-yu/worker-mailer/blob/main/README_zh-CN.md)
-
-## v0.8.6
-
-- feat: |UI| 公告支持 html 格式
-- feat: |UI| `COPYRIGHT` 支持 html 格式
-- feat: |Doc| 优化部署文档,补充了 `Github Actions 部署文档`,增加了 `Worker 变量说明`
-
-## v0.8.5
-
-- feat: |mail-parser-wasm-worker| 修复 `initSync` 函数调用时的 `deprecated` 参数警告
-- feat: rpc headers covert & typo (#559)
-- fix: telegram mail page use iframe show email (#561)
-- feat: |Worker| 增加 `REMOVE_ALL_ATTACHMENT` 和 `REMOVE_EXCEED_SIZE_ATTACHMENT` 用于移除邮件附件,由于是解析邮件的一些信息会丢失,比如图片等.
-
-## v0.8.4
-
-- fix: |UI| 修复 admin portal 无收件人邮箱删除调用api 错误
-- feat: |Telegram Bot| 增加 telegram bot 清理无效地址凭证命令
-- feat: 增加 worker 配置 `DISABLE_ANONYMOUS_USER_CREATE_EMAIL` 禁用匿名用户创建邮箱地址,只允许登录用户创建邮箱地址
-- feat: 增加 worker 配置 `ENABLE_ANOTHER_WORKER` 及 `ANOTHER_WORKER_LIST` ,用于调用其他 worker 的 rpc 接口 (#547)
-- feat: |UI| 自动刷新配置保存到浏览器,可配置刷新间隔
-- feat: 垃圾邮件检测增加存在时才检查的列表 `JUNK_MAIL_CHECK_LIST` 配置
-- feat: | Worker | 增加 `ParsedEmailContext` 类用于缓存解析后的邮件内容,减少解析次数
-- feat: |Github Action| Worker 部署增加 `DEBUG_MODE` 输出日志, `BACKEND_USE_MAIL_WASM_PARSER` 配置是否使用 wasm 解析邮件
-
-## v0.8.3
-
-- feat: |Github Action| 增加自动更新并部署功能
-- feat: |UI| admin 用户设置,支持 oauth2 配置的删除
-- feat: 增加垃圾邮件检测必须通过的列表 `JUNK_MAIL_FORCE_PASS_LIST` 配置
-
-## v0.8.2
-
-- fix: |Doc| 修复文档中的一些错误
-- fix: |Github Action| 修复 frontend 部署分支错误的问题
-- feat: admin 发送邮件功能
-- feat: admin 后台,账号配置页面添加无限发送邮件的地址列表
-
-## v0.8.1
-
-- feat: |Doc| 更新 UI 安装的文档
-- feat: |UI| 对用户隐藏邮箱账号的 ID
-- feat: |UI| 增加邮件详情页的 `转发` 按钮
-
-## v0.8.0
-
-- feat: |UI| 随机生成地址时不超过最大长度
-- feat: |UI| 邮件时间显示浏览器时区,可在设置中切换显示为 UTC 时间
-- feat: 支持转移邮件到其他用户
-
-## v0.7.6
-
-### Breaking Changes
-
-UI 部署 worker 需要点击 Settings -> Runtime, 修改 Compatibility flags, 增加 `nodejs_compat`
-
-
-
-### Changes
-
-- feat: 支持提前设置 bot info, 降低 telegram 回调延迟 (#441)
-- feat: 增加 telegram mini app 的 build 压缩包
-- feat: 增加是否启用垃圾邮件检查 `ENABLE_CHECK_JUNK_MAIL` 配置
-
-## v0.7.5
-
-- fix: 修复 `name` 的校验检查
-
-## v0.7.4
-
-- feat: UI 列表页面增加最小宽度
-- fix: 修复 `name` 的校验检查
-- fix: 修复 `DEFAULT_DOMAINS` 配置为空不生效的问题
-
-## v0.7.3
-
-- feat: worker 增加 `ADDRESS_CHECK_REGEX`, address name 的正则表达式, 只用于检查,符合条件将通过检查
-- fix: UI 修复登录页面 tab 激活图标错位
-- fix: UI 修复 admin 页面刷新弹框输入密码的问题
-- feat: support `Oath2` 登录, 可以通过 `Github` `Authentik` 等第三方登录, 详情查看 [OAuth2 第三方登录](https://temp-mail-docs.awsl.uk/zh/guide/feature/user-oauth2.html)
-
-## v0.7.2
-
-### Breaking Changes
-
-`webhook` 的结构增加了 `enabled` 字段,已经配置了的需要重新在页面开启并保存。
-
-### Changes
-
-- fix: worker 增加 `NO_LIMIT_SEND_ROLE` 配置, 加载失败的问题
-- feat: worker 增加 `# ADDRESS_REGEX = "[^a-z.0-9]"` 配置, 替换非法符号的正则表达式,如果不设置,默认为 [^a-z0-9], 需谨慎使用, 有些符号可能导致无法收件
-- feat: worker 优化 webhook 逻辑, 支持 admin 配置全局 webhook, 添加 `message pusher` 集成示例
-
-## v0.7.1
-
-- fix: 修复用户角色加载失败的问题
-- feat: admin 账号设置增加来源邮件地址黑名单配置
-
-## v0.7.0
-
-### Breaking Changes
-
-DB changes: 增加用户 `passkey` 表, 需要执行 `db/2024-08-10-patch.sql` 更新 `D1` 数据库
-
-### Changes
-
-- Docs: Update new-address-api.md (#360)
-- feat: worker 增加 `ADMIN_USER_ROLE` 配置, 用于配置管理员用户角色,此角色的用户可访问 admin 管理页面 (#363)
-- feat: worker 增加 `DISABLE_SHOW_GITHUB` 配置, 用于配置是否显示 github 链接
-- feat: worker 增加 `NO_LIMIT_SEND_ROLE` 配置, 用于配置可以无限发送邮件的角色
-- feat: 用户增加 `passkey` 登录方式, 用于用户登录, 无需输入密码
-- feat: worker 增加 `DISABLE_ADMIN_PASSWORD_CHECK` 配置, 用于配置是否禁用 admin 控制台密码检查, 若你的网站只可私人访问,可通过此禁用检查
-
-## v0.6.1
-
-- pages github actions && 修复清理邮件天数为 0 不生效 by @tqjason (#355)
-- fix: imap proxy server 不支持 密码 by @dreamhunter2333 (#356)
-- worker 新增 `ANNOUNCEMENT` 配置, 用于配置公告信息 by @dreamhunter2333 (#357)
-- fix: telegram bot 新建地址默认选择第一个域名 by @dreamhunter2333 (#358)
-
-## v0.6.0
-
-### Breaking Changes
-
-DB changes: 增加用户角色表, 需要执行 `db/2024-07-14-patch.sql` 更新 `D1` 数据库
-
-### Changes
-
-worker 配置文件新增 `DEFAULT_DOMAINS`, `USER_ROLES`, `USER_DEFAULT_ROLE`, 具体查看文档 [worker配置](https://temp-mail-docs.awsl.uk/zh/guide/cli/worker.html#%E4%BF%AE%E6%94%B9-wrangler-toml-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6)
-
-- 移除 `apiV1` 相关代码和相关的数据库表
-- 更新 `admin/statistics` api, 添加用户统计信息
-- 更新地址的规则,只允许小写+数字,对于历史的地址在查询邮件时会进行 `lowercase` 处理
-- 增加用户角色功能,`admin` 可以设置用户角色(目前可配置每个角色域名和前缀)
-- admin 页面搜索优化, 回车自动搜索, 输入内容自动 trim
-
-## v0.5.4
-
-- 点击 logo 5 次进入 admin 页面
-- 修复 401 时无法跳转登录页面(admin 和 网站认证)
-
-## v0.5.3
-
-- 修复 smtp imap proxy sever 的一些 bug
-- 完善用户/admin 删除收件箱/发件箱的功能
-- admin 可以删除 发件权限记录
-- 添加中文邮件别名配置 `DOMAIN_LABELS` [文档](https://temp-mail-docs.awsl.uk/zh/guide/cli/worker.html)
-- 移除 `mail channels` 相关代码
-- github actions 增加 `FRONTEND_BRANCH` 变量用于指定部署的分支 (#324)
-
-## v0.5.1
-
-- 添加 `mail-parser-wasm-worker` 用于 worker 解析邮件, [文档](https://temp-mail-docs.awsl.uk/zh/guide/feature/mail_parser_wasm_worker.html)
-- 添加校验用户邮箱长度配置 `MIN_ADDRESS_LEN` 和 `MAX_ADDRESS_LEN`
-- 修复 `pages function` 未转发 `telegram` api 问题
-
-## v0.5.0
-
-- UI: 增加本地缓存进行地址管理
-- worker: 增加 `FORWARD_ADDRESS_LIST` 全局邮件转发地址(等同于 `catch all`)
-- UI: 多语言使用路由进行切换
-- 添加保存附件到 S3 的功能
-- UI: 增加收取邮件列表 `批量删除` 和 `批量下载`
-
-## v0.4.6
-
-- worker 配置文件添加 `TITLE = "Custom Title"`, 可自定义网站标题
-- 修复 KV 未绑定无法删除地址的问题
-
-## v0.4.5
-
-- UI lazy load 懒加载
-- telegram bot 添加用户全局推送功能(admin 用户)
-- 增加对 cloudflare verified 用户发送邮件
-- 增加使用 `resend` 发送邮件, `resend` 提供 http 和 smtp api, 使用更加方便, 文档: https://temp-mail-docs.awsl.uk/zh/guide/config-send-mail.html
-
-## v0.4.4
-
-- 增加 telegram mini app
-- telegram bot 增加 `ubind`, `delete` 指令
-- 修复 webhook 多行文本的问题
-
-## v0.4.3
-
-### Breaking Changes
-
-配置文件 `main = "src/worker.js"` 改为 `main = "src/worker.ts"`
-
-### Changes
-
-- `telegram bot` 白名单配置
-- `ENABLE_WEBHOOK` 添加 webhook
-- UI: admin 页面使用双层 tab
-- UI: 登录后可直接主页切换地址
-- UI: 发件箱也采用左右分栏显示(类似收件箱)
-- `SMTP IMAP Proxy` 添加发件箱查看
-
-* feat: telegram bot TelegramSettings && webhook by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/244
-* fix build by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/245
-* feat: UI changes by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/247
-* feat: SMTP IMAP Proxy: add sendbox && UI: sendbox use split view by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/248
-
-## v0.4.2
-
-- 修复 smtp imap proxy sever 的一些 bug
-- 修复 UI 界面文字错误, 界面增加版本号
-- 增加 telegram bot 文档 https://temp-mail-docs.awsl.uk/zh/guide/feature/telegram.html
-
-* fix: imap server by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/227
-* fix: Maintenance wrong label by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/229
-* feat: add version for frontend && backend by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/230
-* feat: add page functions proxy to make response faster by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/234
-* feat: add about page by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/235
-* feat: remove mailV1Alert && fix mobile showSideMargin by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/236
-* feat: telegram bot by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/238
-* fix: remove cleanup address due to many table need to be clean by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/240
-* feat: docs: Telegram Bot by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/241
-* fix: smtp_proxy: cannot decode 8bit && tg bot new random address by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/242
-* fix: smtp_proxy: update raise imap4.NoSuchMailbox by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/243
-
-### v0.4.1
-
-- 用户名限制最长30个字符
-- 修复 `/external/api/send_mail` 未返回的 bug (#222)
-- 添加 `IMAP proxy` 服务,支持 `IMAP` 查看邮件
-- UI 界面增加版本号显示
-
-* feat: use common function handleListQuery when query by page by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/220
-* fix: typos by @lwd-temp in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/221
-* fix: name max 30 && /external/api/send_mail not return result by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/222
-* fix: smtp_proxy_server support decode from mail charset by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/223
-* feat: add imap proxy server by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/225
-* feat: UI show version by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/226
-
-### New Contributors
-
-* @lwd-temp made their first contribution in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/221
-
-## v0.4.0
-
-### DB Changes/Breaking changes
-
-新增 user 相关表,用于存储用户信息
-
-- `db/2024-05-08-patch.sql`
-
-### config changs
-
-启用用户注册邮箱验证需要 `KV`
-
-```toml
-# kv config for send email verification code
-# [[kv_namespaces]]
-# binding = "KV"
-# id = "xxxx"
-```
-
-### function changs
-
-- 增加用户注册功能,可绑定邮箱地址,绑定后可自动获取邮箱JWT凭证
-- 增加默认以文本显示邮件,文本和HTML邮箱显示方式切换按钮
-- 修复 `BUG` 随机生成的邮箱名字不合法 #211
-- `admin` 邮件页面支持邮件内容搜索 #210
-- 修复删除地址时邮件未删除的BUG #213
-- UI 增加全局标签页位置配置, 侧边距配置
-
-* feat: update docs by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/204
-* feat: add Deploy to Cloudflare Workers button by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/205
-* feat: add Deploy to Cloudflare Workers docs by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/206
-* feat: add UserLogin by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/209
-* feat: admin search mailbox && fix generateName multi dot && user jwt exp in 30 days && UI globalTabplacement && useSideMargin by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/214
-* feat: UI check openSettings in Login page by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/215
-* feat: UI move AdminContact to common by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/217
-* feat: docs by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/218
-
-## v0.3.3
-
-- 修复 Admin 删除邮件报错
-- UI: 回复邮件按钮, 引用原始邮件文本 #186
-- 添加发送邮件地址黑名单
-- 添加 `CF Turnstile` 人机验证配置
-- 添加 `/external/api/send_mail` 发送邮件 api, 使用 body 验证 #194
-
-## v0.3.2
-
-## What's Changed
-
-- UI: 添加回复邮件按钮
-- 添加定时清理功能,可在 admin 页面配置(需要在配置文件启用定时任务)
-- 修复删除账户无反应的问题
-
-* feat: UI: MailBox add reply button by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/187
-* feat: add cron auto clean up by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/189
-* fix: delete account by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/190
-
-## v0.3.1
-
-### DB Changes
-
-新增 `settings` 表,用于存储通用配置信息
-
-- `db/2024-05-01-patch.sql`
-
-### Changes
-
-- `ENABLE_USER_CREATE_EMAIL` 是否允许用户创建邮件
-- 允许 admin 创建无前缀的邮件
-- 添加 `SMTP proxy server`,支持 SMTP 发送邮件
-- 修复某些情况浏览器无法加载 `wasm` 时使用 js 解析邮件
-- 页脚添加 `COPYRIGHT`
-- UI 允许用户切换邮件展示模式 `v-html` / `iframe`
-- 添加 `admin` 账户配置页面,支持配置用户注册名称黑名单
-
-* feat: support admin create address && add ENABLE_USER_CREATE_EMAIL co… by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/175
-* feat: add SMTP proxy server by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/177
-* fix: cf ui var is string by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/178
-* fix: UI mailbox 100vh to 80vh by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/179
-* fix: smtp_proxy_server hostname && add docker image for linux/arm64 by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/180
-* fix: some browser do not support wasm by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/182
-* feat: add COPYRIGHT by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/183
-* feat: UI: add user page: useIframeShowMail && mailboxSplitSize by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/184
-* feat: add address_block_list for new address by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/185
-
-## v0.3.0
-
-### Breaking Changes
-
-`address` 表的前缀将从代码中迁移到 db 中,请将下面 sql 中的 `tmp` 替换为你的前缀,然后执行。
-如果你的数据很重要,请先备份数据库。
-
-**注意替换前缀**
-
-```sql
-update
- address
-set
- name = 'tmp' || name;
-```
-
-### Changes
-
-- `address` 表的前缀将从代码中迁移到 db 中
-- `admin` 账户页面添加收发邮件数量
-- `admin` 发件页面默认显示全部
-- `admin` 发件权限页面支持搜索地址
-- `admin` 邮件页面使用左右分栏 UI
-
-* feat: remove PREFIX logic in db by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/171
-* feat: admin page add account mail count && sendbox default all && sen… by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/172
-* feat: all mail use MailBox Component by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/173
-
-**Full Changelog**: https://github.com/dreamhunter2333/cloudflare_temp_email/compare/0.2.10...v0.3.0
-
-## v0.2.10
-
-- `ENABLE_USER_DELETE_EMAIL` 是否允许用户删除账户和邮件
-- `ENABLE_AUTO_REPLY` 是否启用自动回复
-- fetchAddressError 提示改进
-- 自动刷新显示倒计时
-
-* feat: docs update by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/165
-* feat: add ENABLE_USER_DELETE_EMAIL && ENABLE_AUTO_REPLY && modify fetchAddressError i18n && UI: show autoRefreshInterval by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/169
-
-## v0.2.9
-
-- 添加富文本编辑器
-- admin 联系方式,不配置则不显示,可配置任意字符串 `ADMIN_CONTACT = "xx@xx.xxx"`
-- 默认发送邮件余额,如果不设置,将为 0 `DEFAULT_SEND_BALANCE = 1`
-
-## v0.2.8
-
-- 允许用户删除邮件
-- admin 修改发件权限时邮件通知用户
-- 发件权限默认 1 条
-- 添加 RATE_LIMITER 限流 发送邮件 和 新建地址
-- 一些 bug 修复
-
-- feat: allow user delete mail && notify when send access changed by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/132
-- feat: requset_send_mail_access default 1 balance by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/143
-- fix: RATE_LIMITER not call jwt by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/146
-- fix: delete_address not delete address_sender by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/153
-- fix: send_balance not update when click sendmail by @dreamhunter2333 in https://github.com/dreamhunter2333/cloudflare_temp_email/pull/155
-
-## v0.2.7
-
-- Added user interface installation documentation
-- Support email DKIM
-- Rate limiting configuration for `/api/new_address`
-
-## v0.2.6
-
-- Added admin query outbox page
-- Add admin data cleaning page
-
-## 2024-04-12 v0.2.5
-
-- support send email
-
-DB changes:
-
-- `db/2024-04-12-patch.sql`
-
-## 2024-04-10 v0.2.0
-
-### Breaking Changes
-
-- remove `ENABLE_ATTACHMENT` config
-- use rust wasm to parse email in frontend
-- deprecated api moved to `/api/v1`
-
-### Rust Mail Parser
-
-由于 nodejs 解析 email 的库有些问题,此版本切换到使用 rust wasm 调用 rust 的mail 解析库
-
-- 速度更快,附件支持好,可以显示邮件的附件图片
-- 解析支持更多 rfc 规范
-
-Due to some problems with nodejs' email parsing library, this version switches to using rust wasm to call rust's mail parsing library.
-
-- Faster speed, good attachment support, can display attachment images of emails
-- Parsing supports more rfc specifications
-
-### DB changs
-
-将 `mails` 表废弃,新的 `mail` 的 `raw` 文本将直接存入 `raw_mails` 表.
-The `mails` table will be discarded, and the `raw` text of the new `mail` will be directly stored in the `raw_mails` table
-
-## Upgrade Step
-
-```bash
-git checkout v0.2.0
-cd worker
-wrangler d1 execute dev --file=../db/2024-04-09-patch.sql --remote
-pnpm run deploy
-cd ../frontend
-pnpm run deploy
-```
-
-注意:对于历史邮件,请使用部署新网页查看旧数据。
-Note: For historical messages, use the Deploy New web page to view old data.
-
-```bash
-git checkout feature/backup
-cd frontend
-# 创建一个新的 pages, 用于访问旧数据
-pnpm run deploy --project-name temp-email-v1
-```
-
-## 2024-04-09 v0.0.0
-
-release v0.0.0
-
-## 2024-04-03
-
-DB changes
-
-- `db/2024-04-03-patch.sql`
-
-Changes:
-
-- add delete account
-- add admin panel search
-
-## 2024-01-13
-
-DB changes
-
-- `db/2024-01-13-patch.sql`
diff --git a/cloudflare_temp_email/LICENSE b/cloudflare_temp_email/LICENSE
deleted file mode 100644
index 7fac17a..0000000
--- a/cloudflare_temp_email/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2023 Dream Hunter
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/cloudflare_temp_email/README.md b/cloudflare_temp_email/README.md
deleted file mode 100644
index ebc959d..0000000
--- a/cloudflare_temp_email/README.md
+++ /dev/null
@@ -1,187 +0,0 @@
-
-# Cloudflare 临时邮箱 - 免费搭建临时邮件服务
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 🇨🇳 中文文档 |
- 🇺🇸 English Document
-
-
-> 本项目仅供学习和个人用途,请勿将其用于任何违法行为,否则后果自负。
-
-**🎉 一个功能完整的临时邮箱服务!**
-
-- 🆓 **完全免费** - 基于 Cloudflare 免费服务构建,零成本运行
-- ⚡ **高性能** - Rust WASM 邮件解析,响应速度极快
-- 🎨 **现代化界面** - 响应式设计,支持多语言,操作简便
-
-## 📚 部署文档 - 快速开始
-
-[📖 部署文档](https://temp-mail-docs.awsl.uk) | [🚀 Github Action 部署文档](https://temp-mail-docs.awsl.uk/zh/guide/actions/github-action.html)
-
-
-
-
-
-## 📝 更新日志
-
-查看 [CHANGELOG](CHANGELOG.md) 了解最新更新内容。
-
-## 🎯 在线体验
-
-立即体验 → [https://mail.awsl.uk/](https://mail.awsl.uk/)
-
-
-📊 服务状态监控(点击收缩/展开)
-
-| | |
-| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| [Backend](https://temp-email-api.awsl.uk/) | [](https://github.com/dreamhunter2333/cloudflare_temp_email/actions/workflows/backend_deploy.yaml)       |
-| [Frontend](https://mail.awsl.uk/) | [](https://github.com/dreamhunter2333/cloudflare_temp_email/actions/workflows/frontend_deploy.yaml)       |
-
-
-
-
-⭐ Star History(点击收缩/展开)
-
-
-
-
-
-
-
-
-
-
-📖 目录(点击收缩/展开)
-
-- [Cloudflare 临时邮箱 - 免费搭建临时邮件服务](#cloudflare-临时邮箱---免费搭建临时邮件服务)
- - [📚 部署文档 - 快速开始](#-部署文档---快速开始)
- - [📝 更新日志](#-更新日志)
- - [🎯 在线体验](#-在线体验)
- - [✨ 核心功能](#-核心功能)
- - [📧 邮件处理](#-邮件处理)
- - [👥 用户管理](#-用户管理)
- - [🔧 管理功能](#-管理功能)
- - [🌐 多语言与界面](#-多语言与界面)
- - [🤖 集成与扩展](#-集成与扩展)
- - [🏗️ 技术架构](#️-技术架构)
- - [🏛️ 系统架构](#️-系统架构)
- - [🛠️ 技术栈](#️-技术栈)
- - [📦 主要组件](#-主要组件)
- - [🌟 加入社区](#-加入社区)
-
-
-
-## ✨ 核心功能
-
-
-✨ 核心功能详情(点击收缩/展开)
-
-### 📧 邮件处理
-
-- [x] 使用 `rust wasm` 解析邮件,解析速度快,几乎所有邮件都能解析,node 的解析模块解析邮件失败的邮件,rust wasm 也能解析成功
-- [x] 支持发送邮件,支持 `DKIM` 验证
-- [x] 支持 `SMTP` 和 `Resend` 等多种发送方式
-- [x] 增加查看 `附件` 功能,支持附件图片显示
-- [x] 支持 S3 附件存储和删除功能
-- [x] 垃圾邮件检测和黑白名单配置
-- [x] 邮件转发功能,支持全局转发地址
-
-### 👥 用户管理
-
-- [x] 使用 `凭证` 重新登录之前的邮箱
-- [x] 添加完整的用户注册登录功能,可绑定邮箱地址,绑定后可自动获取邮箱JWT凭证切换不同邮箱
-- [x] 支持 `OAuth2` 第三方登录(Github、Authentik 等)
-- [x] 支持 `Passkey` 无密码登录
-- [x] 用户角色管理,支持多角色域名和前缀配置
-- [x] 用户收件箱查看,支持地址和关键词过滤
-
-### 🔧 管理功能
-
-- [x] 完整的 admin 控制台
-- [x] `admin` 后台创建无前缀邮箱
-- [x] admin 用户管理页面,增加用户地址查看功能
-- [x] 定时清理功能,支持多种清理策略
-- [x] 获取自定义名字的邮箱,`admin` 可配置黑名单
-- [x] 增加访问密码,可作为私人站点
-
-### 🌐 多语言与界面
-
-- [x] 前后台均支持多语言
-- [x] 现代化 UI 设计,支持响应式布局
-- [x] 支持 Google Ads 集成
-- [x] 使用 shadow DOM 防止样式污染
-- [x] 支持 URL JWT 参数自动登录
-
-### 🤖 集成与扩展
-
-- [x] 完整的 `Telegram Bot` 支持,以及 `Telegram` 推送,Telegram Bot 小程序
-- [x] 添加 `SMTP proxy server`,支持 `SMTP` 发送邮件,`IMAP` 查看邮件
-- [x] Webhook 支持,消息推送集成
-- [x] 支持 `CF Turnstile` 人机验证
-- [x] 限流配置,防止滥用
-
-
-
-## 🏗️ 技术架构
-
-
-🏗️ 技术架构详情(点击收缩/展开)
-
-### 🏛️ 系统架构
-
-- **数据库**: Cloudflare D1 作为主数据库
-- **前端部署**: 使用 Cloudflare Pages 部署前端
-- **后端部署**: 使用 Cloudflare Workers 部署后端
-- **邮件转发**: 使用 Cloudflare Email Routing
-
-### 🛠️ 技术栈
-
-- **前端**: Vue 3 + Vite + TypeScript
-- **后端**: TypeScript + Cloudflare Workers
-- **邮件解析**: Rust WASM (mail-parser-wasm)
-- **数据库**: Cloudflare D1 (SQLite)
-- **存储**: Cloudflare KV + R2 (可选 S3)
-- **代理服务**: Python SMTP/IMAP Proxy Server
-
-### 📦 主要组件
-
-- **Worker**: 核心后端服务
-- **Frontend**: Vue 3 用户界面
-- **Mail Parser WASM**: Rust 邮件解析模块
-- **SMTP Proxy Server**: Python 邮件代理服务
-- **Pages Functions**: Cloudflare Pages 中间件
-- **Documentation**: VitePress 文档站点
-
-
-
-## 🌟 加入社区
-
-- [Telegram](https://t.me/cloudflare_temp_email)
diff --git a/cloudflare_temp_email/README_EN.md b/cloudflare_temp_email/README_EN.md
deleted file mode 100644
index 75fe1cf..0000000
--- a/cloudflare_temp_email/README_EN.md
+++ /dev/null
@@ -1,46 +0,0 @@
-
-# Cloudflare Temp Email
-
-
- 🇨🇳 中文 |
- 🇺🇸 English
-
-
-**A fully-featured temporary email service built on Cloudflare's free services.**
-
-> This project is for learning and personal use only.
-
-## 🚀 Quick Start
-
-- [📖 Documentation](https://temp-mail-docs.awsl.uk/en/)
-- [🎯 Live Demo](https://mail.awsl.uk/)
-- [📝 CHANGELOG](CHANGELOG.md)
-
-
-
-
-
-
-
-## ✨ Key Features
-
-- **� Email Processing**: Rust WASM parser, SMTP/IMAP support, attachments, auto-reply
-- **👥 User Management**: OAuth2 login, Passkey authentication, role management
-- **🌐 Admin Panel**: Complete admin console, user management, scheduled cleanup
-- **🤖 Integrations**: Telegram Bot, webhooks, CAPTCHA, rate limiting
-- **� Modern UI**: Multi-language, responsive design, JWT auto-login
-
-## 🏗️ Tech Stack
-
-- **Frontend**: Vue 3 + TypeScript + Vite
-- **Backend**: Cloudflare Workers + D1 Database
-- **Email**: Cloudflare Email Routing + Rust WASM Parser
-- **Storage**: Cloudflare KV + R2 (optional S3)
-
-## 🌟 Community
-
-- [Telegram](https://t.me/cloudflare_temp_email)
-
-## 📄 License
-
-MIT License - see [LICENSE](LICENSE) for details.
diff --git a/cloudflare_temp_email/db/2024-01-13-patch.sql b/cloudflare_temp_email/db/2024-01-13-patch.sql
deleted file mode 100644
index 5394129..0000000
--- a/cloudflare_temp_email/db/2024-01-13-patch.sql
+++ /dev/null
@@ -1,4 +0,0 @@
-ALTER TABLE
- mails
-ADD
- message_id TEXT;
diff --git a/cloudflare_temp_email/db/2024-04-03-patch.sql b/cloudflare_temp_email/db/2024-04-03-patch.sql
deleted file mode 100644
index d79c5ae..0000000
--- a/cloudflare_temp_email/db/2024-04-03-patch.sql
+++ /dev/null
@@ -1,4 +0,0 @@
-ALTER TABLE
- address
-ADD
- updated_at DATETIME;
diff --git a/cloudflare_temp_email/db/2024-04-09-patch.sql b/cloudflare_temp_email/db/2024-04-09-patch.sql
deleted file mode 100644
index 27999d3..0000000
--- a/cloudflare_temp_email/db/2024-04-09-patch.sql
+++ /dev/null
@@ -1,8 +0,0 @@
-CREATE TABLE IF NOT EXISTS raw_mails (
- id INTEGER PRIMARY KEY,
- message_id TEXT,
- source TEXT,
- address TEXT,
- raw TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
diff --git a/cloudflare_temp_email/db/2024-04-12-patch.sql b/cloudflare_temp_email/db/2024-04-12-patch.sql
deleted file mode 100644
index b5874f4..0000000
--- a/cloudflare_temp_email/db/2024-04-12-patch.sql
+++ /dev/null
@@ -1,14 +0,0 @@
-CREATE TABLE IF NOT EXISTS address_sender (
- id INTEGER PRIMARY KEY,
- address TEXT UNIQUE,
- balance INTEGER DEFAULT 0,
- enabled INTEGER DEFAULT 1,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE TABLE IF NOT EXISTS sendbox (
- id INTEGER PRIMARY KEY,
- address TEXT,
- raw TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
diff --git a/cloudflare_temp_email/db/2024-05-01-patch.sql b/cloudflare_temp_email/db/2024-05-01-patch.sql
deleted file mode 100644
index 542d268..0000000
--- a/cloudflare_temp_email/db/2024-05-01-patch.sql
+++ /dev/null
@@ -1,6 +0,0 @@
-CREATE TABLE IF NOT EXISTS settings (
- key TEXT PRIMARY KEY,
- value TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
diff --git a/cloudflare_temp_email/db/2024-05-08-patch.sql b/cloudflare_temp_email/db/2024-05-08-patch.sql
deleted file mode 100644
index ac3b560..0000000
--- a/cloudflare_temp_email/db/2024-05-08-patch.sql
+++ /dev/null
@@ -1,21 +0,0 @@
-CREATE TABLE IF NOT EXISTS users (
- id INTEGER PRIMARY KEY,
- user_email TEXT UNIQUE NOT NULL,
- password TEXT NOT NULL,
- user_info TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_users_user_email ON users(user_email);
-
-CREATE TABLE IF NOT EXISTS users_address (
- id INTEGER PRIMARY KEY,
- user_id INTEGER,
- address_id INTEGER UNIQUE,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_users_address_user_id ON users_address(user_id);
-
-CREATE INDEX IF NOT EXISTS idx_users_address_address_id ON users_address(address_id);
diff --git a/cloudflare_temp_email/db/2024-07-14-patch.sql b/cloudflare_temp_email/db/2024-07-14-patch.sql
deleted file mode 100644
index 29c16fe..0000000
--- a/cloudflare_temp_email/db/2024-07-14-patch.sql
+++ /dev/null
@@ -1,9 +0,0 @@
-CREATE TABLE IF NOT EXISTS user_roles (
- id INTEGER PRIMARY KEY,
- user_id INTEGER UNIQUE NOT NULL,
- role_text TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id);
diff --git a/cloudflare_temp_email/db/2024-08-10-patch.sql b/cloudflare_temp_email/db/2024-08-10-patch.sql
deleted file mode 100644
index 38a1241..0000000
--- a/cloudflare_temp_email/db/2024-08-10-patch.sql
+++ /dev/null
@@ -1,14 +0,0 @@
-CREATE TABLE IF NOT EXISTS user_passkeys (
- id INTEGER PRIMARY KEY,
- user_id INTEGER NOT NULL,
- passkey_name TEXT NOT NULL,
- passkey_id TEXT NOT NULL,
- passkey TEXT NOT NULL,
- counter INTEGER DEFAULT 0,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_user_passkeys_user_id ON user_passkeys(user_id);
-
-CREATE UNIQUE INDEX IF NOT EXISTS idx_user_passkeys_user_id_passkey_id ON user_passkeys(user_id, passkey_id);
diff --git a/cloudflare_temp_email/db/schema.sql b/cloudflare_temp_email/db/schema.sql
deleted file mode 100644
index 153f66f..0000000
--- a/cloudflare_temp_email/db/schema.sql
+++ /dev/null
@@ -1,105 +0,0 @@
-CREATE TABLE IF NOT EXISTS raw_mails (
- id INTEGER PRIMARY KEY,
- message_id TEXT,
- source TEXT,
- address TEXT,
- raw TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_raw_mails_address ON raw_mails(address);
-
-CREATE TABLE IF NOT EXISTS address (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT UNIQUE,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_address_name ON address(name);
-
-CREATE TABLE IF NOT EXISTS auto_reply_mails (
- id INTEGER PRIMARY KEY,
- source_prefix TEXT,
- name TEXT,
- address TEXT UNIQUE,
- subject TEXT,
- message TEXT,
- enabled INTEGER DEFAULT 1,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_auto_reply_mails_address ON auto_reply_mails(address);
-
-CREATE TABLE IF NOT EXISTS address_sender (
- id INTEGER PRIMARY KEY,
- address TEXT UNIQUE,
- balance INTEGER DEFAULT 0,
- enabled INTEGER DEFAULT 1,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_address_sender_address ON address_sender(address);
-
-CREATE TABLE IF NOT EXISTS sendbox (
- id INTEGER PRIMARY KEY,
- address TEXT,
- raw TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_sendbox_address ON sendbox(address);
-
-CREATE TABLE IF NOT EXISTS settings (
- key TEXT PRIMARY KEY,
- value TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE TABLE IF NOT EXISTS users (
- id INTEGER PRIMARY KEY,
- user_email TEXT UNIQUE NOT NULL,
- password TEXT NOT NULL,
- user_info TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_users_user_email ON users(user_email);
-
-CREATE TABLE IF NOT EXISTS users_address (
- id INTEGER PRIMARY KEY,
- user_id INTEGER,
- address_id INTEGER UNIQUE,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_users_address_user_id ON users_address(user_id);
-
-CREATE INDEX IF NOT EXISTS idx_users_address_address_id ON users_address(address_id);
-
-CREATE TABLE IF NOT EXISTS user_roles (
- id INTEGER PRIMARY KEY,
- user_id INTEGER UNIQUE NOT NULL,
- role_text TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id);
-
-CREATE TABLE IF NOT EXISTS user_passkeys (
- id INTEGER PRIMARY KEY,
- user_id INTEGER NOT NULL,
- passkey_name TEXT NOT NULL,
- passkey_id TEXT NOT NULL,
- passkey TEXT NOT NULL,
- counter INTEGER DEFAULT 0,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_user_passkeys_user_id ON user_passkeys(user_id);
-
-CREATE UNIQUE INDEX IF NOT EXISTS idx_user_passkeys_user_id_passkey_id ON user_passkeys(user_id, passkey_id);
diff --git a/cloudflare_temp_email/frontend/.env.example b/cloudflare_temp_email/frontend/.env.example
deleted file mode 100644
index e2bdffe..0000000
--- a/cloudflare_temp_email/frontend/.env.example
+++ /dev/null
@@ -1,3 +0,0 @@
-VITE_API_BASE=https://temp-email-api.xxx.xxx
-VITE_CF_WEB_ANALY_TOKEN=
-VITE_IS_TELEGRAM=false
diff --git a/cloudflare_temp_email/frontend/.env.pages b/cloudflare_temp_email/frontend/.env.pages
deleted file mode 100644
index 7a56d96..0000000
--- a/cloudflare_temp_email/frontend/.env.pages
+++ /dev/null
@@ -1,2 +0,0 @@
-VITE_API_BASE=
-VITE_CF_WEB_ANALY_TOKEN=
diff --git a/cloudflare_temp_email/frontend/README.md b/cloudflare_temp_email/frontend/README.md
deleted file mode 100644
index 2a46112..0000000
--- a/cloudflare_temp_email/frontend/README.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# cloudflare_temp_email
-
-This template should help get you started developing with Vue 3 in Vite.
-
-## Recommended IDE Setup
-
-[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
-
-## Customize configuration
-
-See [Vite Configuration Reference](https://vitejs.dev/config/).
-
-## Project Setup
-
-```sh
-npm install
-```
-
-### Compile and Hot-Reload for Development
-
-```sh
-npm run dev
-```
-
-### Compile and Minify for Production
-
-```sh
-npm run build
-```
diff --git a/cloudflare_temp_email/frontend/index.html b/cloudflare_temp_email/frontend/index.html
deleted file mode 100644
index f32967b..0000000
--- a/cloudflare_temp_email/frontend/index.html
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
- Temp Email
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/package.json b/cloudflare_temp_email/frontend/package.json
deleted file mode 100644
index 7e0c2c3..0000000
--- a/cloudflare_temp_email/frontend/package.json
+++ /dev/null
@@ -1,53 +0,0 @@
-{
- "name": "cloudflare_temp_email",
- "version": "1.0.5",
- "private": true,
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "vite build -m prod --emptyOutDir",
- "build:release": "vite build -m example --emptyOutDir",
- "build:pages": "vite build -m pages --emptyOutDir",
- "build:pages:nopwa": "VITE_PWA_DISABLED=true vite build -m pages --emptyOutDir",
- "build:telegram": "VITE_IS_TELEGRAM=true vite build -m prod --emptyOutDir",
- "build:telegram:release": "VITE_IS_TELEGRAM=true vite build -m example --emptyOutDir",
- "preview": "vite preview",
- "deploy:telegram": "npm run build:telegram && wrangler pages deploy ./dist --branch production",
- "deploy:actions:telegram": "npm run build:telegram && wrangler pages deploy ./dist",
- "deploy:preview": "npm run build && wrangler pages deploy ./dist --branch preview",
- "deploy": "npm run build && wrangler pages deploy ./dist --branch production",
- "deploy:actions": "npm run build && wrangler pages deploy ./dist"
- },
- "dependencies": {
- "@simplewebauthn/browser": "10.0.0",
- "@unhead/vue": "^1.11.20",
- "@vueuse/core": "^12.8.2",
- "@wangeditor/editor": "^5.1.23",
- "@wangeditor/editor-for-vue": "^5.1.12",
- "axios": "^1.11.0",
- "jszip": "^3.10.1",
- "mail-parser-wasm": "^0.2.1",
- "naive-ui": "^2.42.0",
- "postal-mime": "^2.4.4",
- "vooks": "^0.2.12",
- "vue": "^3.5.21",
- "vue-clipboard3": "^2.0.0",
- "vue-i18n": "^11.1.11",
- "vue-router": "^4.5.1"
- },
- "devDependencies": {
- "@vicons/fa": "^0.13.0",
- "@vicons/material": "^0.13.0",
- "@vitejs/plugin-vue": "^5.2.4",
- "unplugin-auto-import": "^19.3.0",
- "unplugin-vue-components": "^28.8.0",
- "vite": "^6.3.5",
- "vite-plugin-pwa": "^1.0.3",
- "vite-plugin-top-level-await": "^1.6.0",
- "vite-plugin-wasm": "^3.5.0",
- "workbox-build": "^7.3.0",
- "workbox-window": "^7.3.0",
- "wrangler": "^4.34.0"
- },
- "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
-}
diff --git a/cloudflare_temp_email/frontend/public/logo.png b/cloudflare_temp_email/frontend/public/logo.png
deleted file mode 100644
index 97829b1..0000000
Binary files a/cloudflare_temp_email/frontend/public/logo.png and /dev/null differ
diff --git a/cloudflare_temp_email/frontend/src/App.vue b/cloudflare_temp_email/frontend/src/App.vue
deleted file mode 100644
index 35c621a..0000000
--- a/cloudflare_temp_email/frontend/src/App.vue
+++ /dev/null
@@ -1,143 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/api/index.js b/cloudflare_temp_email/frontend/src/api/index.js
deleted file mode 100644
index 2e1bfb7..0000000
--- a/cloudflare_temp_email/frontend/src/api/index.js
+++ /dev/null
@@ -1,206 +0,0 @@
-import { useGlobalState } from '../store'
-import { h } from 'vue'
-import axios from 'axios'
-
-import i18n from '../i18n'
-
-const API_BASE = import.meta.env.VITE_API_BASE || "";
-const {
- loading, auth, jwt, settings, openSettings,
- userOpenSettings, userSettings, announcement,
- showAuth, adminAuth, showAdminAuth, userJwt
-} = useGlobalState();
-
-const instance = axios.create({
- baseURL: API_BASE,
- timeout: 30000,
- validateStatus: (status) => status >= 200 && status <= 500
-});
-
-const apiFetch = async (path, options = {}) => {
- loading.value = true;
- try {
- const response = await instance.request(path, {
- method: options.method || 'GET',
- data: options.body || null,
- headers: {
- 'x-lang': i18n.global.locale.value,
- 'x-user-token': options.userJwt || userJwt.value,
- 'x-user-access-token': userSettings.value.access_token,
- 'x-custom-auth': auth.value,
- 'x-admin-auth': adminAuth.value,
- 'Authorization': `Bearer ${jwt.value}`,
- 'Content-Type': 'application/json',
- },
- });
- if (response.status === 401 && path.startsWith("/admin")) {
- showAdminAuth.value = true;
- }
- if (response.status === 401 && openSettings.value.auth) {
- showAuth.value = true;
- }
- if (response.status >= 300) {
- throw new Error(`[${response.status}]: ${response.data}` || "error");
- }
- const data = response.data;
- return data;
- } catch (error) {
- if (error.response) {
- throw new Error(`Code ${error.response.status}: ${error.response.data}` || "error");
- }
- throw error;
- } finally {
- loading.value = false;
- }
-}
-
-const getOpenSettings = async (message, notification) => {
- try {
- const res = await api.fetch("/open_api/settings");
- const domainLabels = res["domainLabels"] || [];
- if (res["domains"]?.length < 1) {
- message.error("No domains found, please check your worker settings");
- }
- Object.assign(openSettings.value, {
- ...res,
- title: res["title"] || "",
- prefix: res["prefix"] || "",
- minAddressLen: res["minAddressLen"] || 1,
- maxAddressLen: res["maxAddressLen"] || 30,
- needAuth: res["needAuth"] || false,
- defaultDomains: res["defaultDomains"] || [],
- domains: res["domains"].map((domain, index) => {
- return {
- label: domainLabels.length > index ? domainLabels[index] : domain,
- value: domain
- }
- }),
- adminContact: res["adminContact"] || "",
- enableUserCreateEmail: res["enableUserCreateEmail"] || false,
- disableAnonymousUserCreateEmail: res["disableAnonymousUserCreateEmail"] || false,
- disableCustomAddressName: res["disableCustomAddressName"] || false,
- enableUserDeleteEmail: res["enableUserDeleteEmail"] || false,
- enableAutoReply: res["enableAutoReply"] || false,
- enableIndexAbout: res["enableIndexAbout"] || false,
- copyright: res["copyright"] || openSettings.value.copyright,
- cfTurnstileSiteKey: res["cfTurnstileSiteKey"] || "",
- enableWebhook: res["enableWebhook"] || false,
- isS3Enabled: res["isS3Enabled"] || false,
- });
- if (openSettings.value.needAuth) {
- showAuth.value = true;
- }
- if (openSettings.value.announcement
- && !openSettings.value.fetched
- && (openSettings.value.announcement != announcement.value
- || openSettings.value.alwaysShowAnnouncement)
- ) {
- announcement.value = openSettings.value.announcement;
- notification.info({
- content: () => {
- return h("div", {
- innerHTML: announcement.value
- });
- }
- });
- }
- } catch (error) {
- message.error(error.message || "error");
- } finally {
- openSettings.value.fetched = true;
- }
-}
-
-const getSettings = async () => {
- try {
- if (typeof jwt.value != 'string' || jwt.value.trim() === '' || jwt.value === 'undefined') {
- return "";
- }
- const res = await apiFetch("/api/settings");;
- settings.value = {
- address: res["address"],
- auto_reply: res["auto_reply"],
- send_balance: res["send_balance"],
- };
- } finally {
- settings.value.fetched = true;
- }
-}
-
-
-const getUserOpenSettings = async (message) => {
- try {
- const res = await api.fetch(`/user_api/open_settings`);
- Object.assign(userOpenSettings.value, res);
- } catch (error) {
- message.error(error.message || "fetch settings failed");
- } finally {
- userOpenSettings.value.fetched = true;
- }
-}
-
-const getUserSettings = async (message) => {
- try {
- if (!userJwt.value) return;
- const res = await api.fetch("/user_api/settings")
- Object.assign(userSettings.value, res)
- // auto refresh user jwt
- if (userSettings.value.new_user_token) {
- try {
- await api.fetch("/user_api/settings", {
- userJwt: userSettings.value.new_user_token,
- })
- userJwt.value = userSettings.value.new_user_token;
- console.log("User JWT updated successfully");
- }
- catch (error) {
- console.error("Failed to update user JWT", error);
- }
- }
- } catch (error) {
- message?.error(error.message || "error");
- } finally {
- userSettings.value.fetched = true;
- }
-}
-
-const adminShowAddressCredential = async (id) => {
- try {
- const { jwt: addressCredential } = await apiFetch(`/admin/show_password/${id}`);
- return addressCredential;
- } catch (error) {
- throw error;
- }
-}
-
-const adminDeleteAddress = async (id) => {
- try {
- await apiFetch(`/admin/delete_address/${id}`, {
- method: 'DELETE'
- });
- } catch (error) {
- throw error;
- }
-}
-
-const bindUserAddress = async () => {
- if (!userJwt.value) return;
- try {
- await apiFetch(`/user_api/bind_address`, {
- method: 'POST',
- });
- } catch (error) {
- throw error;
- }
-}
-
-export const api = {
- fetch: apiFetch,
- getSettings,
- getOpenSettings,
- getUserOpenSettings,
- getUserSettings,
- adminShowAddressCredential,
- adminDeleteAddress,
- bindUserAddress,
-}
diff --git a/cloudflare_temp_email/frontend/src/components/MailBox.vue b/cloudflare_temp_email/frontend/src/components/MailBox.vue
deleted file mode 100644
index 0da47f1..0000000
--- a/cloudflare_temp_email/frontend/src/components/MailBox.vue
+++ /dev/null
@@ -1,587 +0,0 @@
-
-
-
-
-
-
-
-
- {{ t('cancelMultiAction') }}
-
-
- {{ t('selectAll') }}
-
-
- {{ t('unselectAll') }}
-
-
-
- {{ t('delete') }}
-
- {{ t('deleteMailTip') }}
-
-
-
-
-
- {{ t('downloadMail') }}
-
-
-
-
- {{ t('multiAction') }}
-
-
-
-
- {{ t('refreshAfter', { msg: autoRefreshInterval }) }}
-
-
- {{ t('autoRefresh') }}
-
-
-
- {{ t('refresh') }}
-
-
-
-
-
-
-
- clickRow(row)"
- :class="mailItemClass(row)">
-
-
-
-
-
-
- ID: {{ row.id }}
-
-
- {{ utcToLocalDate(row.created_at, useUTCDate) }}
-
-
-
- {{ showEMailTo ? "FROM: " + row.source : row.source }}
-
-
-
-
- TO: {{ row.address }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('prevMail') }}
-
-
-
-
-
-
-
- {{ t('nextMail') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('refreshAfter', { msg: autoRefreshInterval }) }}
-
-
- {{ t('autoRefresh') }}
-
-
-
- {{ t('refresh') }}
-
-
-
-
- clickRow(row)">
-
-
-
- ID: {{ row.id }}
-
-
- {{ utcToLocalDate(row.created_at, useUTCDate) }}
-
-
- {{ showEMailTo ? "FROM: " + row.source : row.source }}
-
-
- TO: {{ row.address }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ multiActionDownloadZip.filename }}
-
-
-
- {{ t('downloadMail') + " zip" }}
-
-
-
-
-
-
- {{ multiActionDeleteProgress.tip }}
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/components/MailContentRenderer.vue b/cloudflare_temp_email/frontend/src/components/MailContentRenderer.vue
deleted file mode 100644
index ab5b143..0000000
--- a/cloudflare_temp_email/frontend/src/components/MailContentRenderer.vue
+++ /dev/null
@@ -1,282 +0,0 @@
-
-
-
-
-
-
-
- ID: {{ mail.id }}
-
-
- {{ utcToLocalDate(mail.created_at, useUTCDate.value) }}
-
-
- FROM: {{ mail.source }}
-
-
- TO: {{ mail.address }}
-
-
-
-
-
- {{ t('delete') }}
-
- {{ t('deleteMailTip') }}
-
-
-
- {{ t('attachments') }}
-
-
-
-
-
-
- {{ t('downloadMail') }}
-
-
-
-
-
-
- {{ t('reply') }}
-
-
-
-
-
-
- {{ t('forward') }}
-
-
-
- {{ showTextMail ? t('showHtmlMail') : t('showTextMail') }}
-
-
-
-
-
-
- {{ t('fullscreen') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('attachments') }}
-
-
-
-
-
-
-
-
- Size: {{ row.size }}
-
-
- {{ t('saveToS3') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/components/SendBox.vue b/cloudflare_temp_email/frontend/src/components/SendBox.vue
deleted file mode 100644
index 8da45a9..0000000
--- a/cloudflare_temp_email/frontend/src/components/SendBox.vue
+++ /dev/null
@@ -1,405 +0,0 @@
-
-
-
-
-
-
-
-
- {{ t('cancelMultiAction') }}
-
-
- {{ t('selectAll') }}
-
-
- {{ t('unselectAll') }}
-
-
-
- {{ t('delete') }}
-
- {{ t('deleteMailTip') }}
-
-
-
-
- {{ t('multiAction') }}
-
-
-
-
-
- {{ t('refresh') }}
-
-
-
-
-
-
-
- clickRow(row)"
- :class="mailItemClass(row)">
-
-
-
-
-
-
- ID: {{ row.id }}
-
-
- {{ utcToLocalDate(row.created_at, useUTCDate) }}
-
-
- FROM: {{ row.address }}
-
-
- TO: {{ row.to_mail }}
-
-
-
-
-
-
-
-
-
-
-
- ID: {{ curMail.id }}
-
-
- {{ utcToLocalDate(curMail.created_at, useUTCDate) }}
-
-
- FROM: {{ curMail.address }}
-
-
- TO: {{ curMail.to_mail }}
-
-
- {{ t('showCode') }}
-
-
-
- {{ t('delete') }}
-
- {{ t('deleteMailTip') }}
-
-
- {{ curMail.raw }}
- {{ curMail.content }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('refresh') }}
-
-
-
-
- clickRow(row)">
-
-
-
- ID: {{ row.id }}
-
-
- {{ utcToLocalDate(row.created_at, useUTCDate) }}
-
-
- FROM: {{ row.address }}
-
-
- TO: {{ row.to_mail }}
-
-
-
-
-
-
-
-
-
-
-
- ID: {{ curMail.id }}
-
-
- {{ utcToLocalDate(curMail.created_at, useUTCDate) }}
-
-
- FROM: {{ curMail.address }}
-
-
- TO: {{ curMail.to_mail }}
-
-
- {{ t('showCode') }}
-
-
-
- {{ t('delete') }}
-
- {{ t('deleteMailTip') }}
-
-
- {{ curMail.raw }}
- {{ curMail.content }}
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/components/ShadowHtmlComponent.vue b/cloudflare_temp_email/frontend/src/components/ShadowHtmlComponent.vue
deleted file mode 100644
index 7340712..0000000
--- a/cloudflare_temp_email/frontend/src/components/ShadowHtmlComponent.vue
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/components/Turnstile.vue b/cloudflare_temp_email/frontend/src/components/Turnstile.vue
deleted file mode 100644
index 89df493..0000000
--- a/cloudflare_temp_email/frontend/src/components/Turnstile.vue
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
-
-
-
-
-
-
- {{ t('refresh') }}
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/components/WebhookComponent.vue b/cloudflare_temp_email/frontend/src/components/WebhookComponent.vue
deleted file mode 100644
index 0361ce8..0000000
--- a/cloudflare_temp_email/frontend/src/components/WebhookComponent.vue
+++ /dev/null
@@ -1,179 +0,0 @@
-
-
-
-
-
-
-
- {{ t('messagePusherDoc') }}
-
-
- {{ t('messagePusherDemo') }}
-
-
- {{ t('test') }}
-
-
- {{ t('save') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/constant/index.ts b/cloudflare_temp_email/frontend/src/constant/index.ts
deleted file mode 100644
index 36cb8ab..0000000
--- a/cloudflare_temp_email/frontend/src/constant/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-const COMMOM_MAIL = [
- "gmail.com", "163.com", "126.com", "qq.com", "outlook.com", "hotmail.com",
- "icloud.com", "yahoo.com", "foxmail.com"
-]
-
-export default {
- COMMOM_MAIL
-}
diff --git a/cloudflare_temp_email/frontend/src/i18n.ts b/cloudflare_temp_email/frontend/src/i18n.ts
deleted file mode 100644
index c11520a..0000000
--- a/cloudflare_temp_email/frontend/src/i18n.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createI18n } from 'vue-i18n'
-
-const i18n = createI18n({
- legacy: false, // you must set `false`, to use Composition API
- locale: 'zh', // set locale
- fallbackLocale: 'en', // set fallback locale
- 'en': {
- messages: {}
- },
- 'zh': {
- messages: {}
- }
-})
-
-export default i18n;
diff --git a/cloudflare_temp_email/frontend/src/main.js b/cloudflare_temp_email/frontend/src/main.js
deleted file mode 100644
index 45271d0..0000000
--- a/cloudflare_temp_email/frontend/src/main.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { createApp } from 'vue'
-import { createHead } from '@unhead/vue'
-
-import App from './App.vue'
-import router from './router'
-import i18n from './i18n'
-
-const head = createHead()
-const app = createApp(App)
-app.use(i18n)
-app.use(router)
-app.use(head)
-app.mount('#app')
diff --git a/cloudflare_temp_email/frontend/src/models/index.ts b/cloudflare_temp_email/frontend/src/models/index.ts
deleted file mode 100644
index 1fbaf1a..0000000
--- a/cloudflare_temp_email/frontend/src/models/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-export type UserOauth2Settings = {
- name: string;
- clientID: string;
- clientSecret: string;
- authorizationURL: string;
- accessTokenURL: string;
- accessTokenFormat?: string;
- userInfoURL: string;
- redirectURL: string;
- logoutURL?: string;
- userEmailKey: string;
- scope: string;
- enableMailAllowList?: boolean | undefined;
- mailAllowList?: string[] | undefined;
-}
diff --git a/cloudflare_temp_email/frontend/src/router/index.js b/cloudflare_temp_email/frontend/src/router/index.js
deleted file mode 100644
index 078464b..0000000
--- a/cloudflare_temp_email/frontend/src/router/index.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import { createRouter, createWebHistory } from 'vue-router'
-import Index from '../views/Index.vue'
-import User from '../views/User.vue'
-import UserOauth2Callback from '../views/user/UserOauth2Callback.vue'
-import i18n from '../i18n'
-import { useGlobalState } from '../store'
-
-const { jwt } = useGlobalState()
-
-const router = createRouter({
- history: createWebHistory(),
- routes: [
- {
- path: '/',
- alias: "/:lang/",
- component: Index
- },
- {
- path: '/user',
- alias: "/:lang/user",
- component: User
- },
- {
- path: '/user/oauth2/callback',
- alias: "/:lang/user/oauth2/callback",
- component: UserOauth2Callback
- },
- {
- path: '/admin',
- alias: "/:lang/admin",
- component: () => import('../views/Admin.vue')
- },
- {
- path: '/telegram_mail',
- alias: "/:lang/telegram_mail",
- component: () => import('../views/telegram/Mail.vue')
- },
- {
- name: 'not-found',
- path: '/:pathMatch(.*)*',
- redirect: '/'
- }
- ]
-});
-
-
-router.beforeEach((to, from, next) => {
- if (to.params.lang && ['en', 'zh'].includes(to.params.lang)) {
- i18n.global.locale.value = to.params.lang
- } else {
- i18n.global.locale.value = 'zh'
- }
- // check if query parameter has jwt, set it to store
- if (to.query.jwt) {
- jwt.value = to.query.jwt;
- }
- next()
-});
-
-export default router
diff --git a/cloudflare_temp_email/frontend/src/store/index.js b/cloudflare_temp_email/frontend/src/store/index.js
deleted file mode 100644
index a5a18f7..0000000
--- a/cloudflare_temp_email/frontend/src/store/index.js
+++ /dev/null
@@ -1,150 +0,0 @@
-import { computed, ref } from "vue";
-import {
- createGlobalState, useStorage, useDark, useToggle,
- useLocalStorage, useSessionStorage
-} from '@vueuse/core'
-
-export const useGlobalState = createGlobalState(
- () => {
- const isDark = useDark()
- const toggleDark = useToggle(isDark)
- const loading = ref(false);
- const announcement = useLocalStorage('announcement', '');
- const useSimpleIndex = useLocalStorage('useSimpleIndex', false);
- const openSettings = ref({
- fetched: false,
- title: '',
- announcement: '',
- alwaysShowAnnouncement: false,
- prefix: '',
- addressRegex: '',
- needAuth: false,
- adminContact: '',
- enableUserCreateEmail: false,
- disableAnonymousUserCreateEmail: false,
- disableCustomAddressName: false,
- enableUserDeleteEmail: false,
- enableAutoReply: false,
- enableIndexAbout: false,
- /** @type {string[]} */
- defaultDomains: [],
- /** @type {Array<{label: string, value: string}>} */
- domains: [],
- copyright: 'Dream Hunter',
- cfTurnstileSiteKey: '',
- enableWebhook: false,
- isS3Enabled: false,
- showGithub: true,
- disableAdminPasswordCheck: false,
- })
- const settings = ref({
- fetched: false,
- send_balance: 0,
- address: '',
- auto_reply: {
- subject: '',
- message: '',
- enabled: false,
- source_prefix: '',
- name: '',
- }
- });
- const sendMailModel = useSessionStorage('sendMailModel', {
- fromName: "",
- toName: "",
- toMail: "",
- subject: "",
- contentType: 'text',
- content: "",
- });
- const showAuth = ref(false);
- const showAddressCredential = ref(false);
- const showAdminAuth = ref(false);
- const auth = useStorage('auth', '');
- const adminAuth = useStorage('adminAuth', '');
- const jwt = useStorage('jwt', '');
- const adminTab = useSessionStorage('adminTab', "account");
- const adminMailTabAddress = ref("");
- const adminSendBoxTabAddress = ref("");
- const mailboxSplitSize = useStorage('mailboxSplitSize', 0.25);
- const useIframeShowMail = useStorage('useIframeShowMail', false);
- const preferShowTextMail = useStorage('preferShowTextMail', false);
- const userJwt = useStorage('userJwt', '');
- const userTab = useSessionStorage('userTab', 'address_management');
- const indexTab = useSessionStorage('indexTab', 'mailbox');
- const globalTabplacement = useStorage('globalTabplacement', 'top');
- const useSideMargin = useStorage('useSideMargin', true);
- const useUTCDate = useStorage('useUTCDate', false);
- const autoRefresh = useStorage('autoRefresh', false);
- const configAutoRefreshInterval = useStorage("configAutoRefreshInterval", 60);
- const userOpenSettings = ref({
- fetched: false,
- enable: false,
- enableMailVerify: false,
- /** @type {{ clientID: string, name: string }[]} */
- oauth2ClientIDs: [],
- });
- const userSettings = ref({
- /** @type {boolean} */
- fetched: false,
- /** @type {string} */
- user_email: '',
- /** @type {number} */
- user_id: 0,
- /** @type {boolean} */
- is_admin: false,
- /** @type {string | null} */
- access_token: null,
- /** @type {string | null} */
- new_user_token: null,
- /** @type {null | {domains: string[] | undefined | null, role: string, prefix: string | undefined | null}} */
- user_role: null,
- });
- const showAdminPage = computed(() =>
- !!adminAuth.value
- || userSettings.value.is_admin
- || openSettings.value.disableAdminPasswordCheck
- );
- const telegramApp = ref(window.Telegram?.WebApp || {});
- const isTelegram = ref(!!window.Telegram?.WebApp?.initData);
- const userOauth2SessionState = useSessionStorage('userOauth2SessionState', '');
- const userOauth2SessionClientID = useSessionStorage('userOauth2SessionClientID', '');
- return {
- isDark,
- toggleDark,
- loading,
- settings,
- sendMailModel,
- announcement,
- openSettings,
- showAuth,
- showAddressCredential,
- auth,
- jwt,
- adminAuth,
- showAdminAuth,
- adminTab,
- adminMailTabAddress,
- adminSendBoxTabAddress,
- mailboxSplitSize,
- useIframeShowMail,
- preferShowTextMail,
- userJwt,
- userTab,
- indexTab,
- userOpenSettings,
- userSettings,
- globalTabplacement,
- useSideMargin,
- useUTCDate,
- autoRefresh,
- configAutoRefreshInterval,
- telegramApp,
- isTelegram,
- showAdminPage,
- userOauth2SessionState,
- userOauth2SessionClientID,
- useSimpleIndex,
- }
- },
-)
diff --git a/cloudflare_temp_email/frontend/src/utils/composables.js b/cloudflare_temp_email/frontend/src/utils/composables.js
deleted file mode 100644
index 85c3184..0000000
--- a/cloudflare_temp_email/frontend/src/utils/composables.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { useBreakpoint, useMemo } from 'vooks'
-
-export function useIsMobile() {
- const breakpointRef = useBreakpoint()
- return useMemo(() => {
- return breakpointRef.value === 'xs'
- })
-}
diff --git a/cloudflare_temp_email/frontend/src/utils/email-parser.js b/cloudflare_temp_email/frontend/src/utils/email-parser.js
deleted file mode 100644
index f25b095..0000000
--- a/cloudflare_temp_email/frontend/src/utils/email-parser.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import PostalMime from 'postal-mime';
-
-function humanFileSize(size) {
- const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
- return parseFloat((size / Math.pow(1024, i)).toFixed(2)) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
-}
-
-export async function processItem(item) {
- // Try to parse the email using mail-parser-wasm
- item.originalSource = item.source;
- try {
- const { parse_message } = await import('mail-parser-wasm');
- const parsedEmail = parse_message(item.raw);
- item.source = parsedEmail.sender || item.source;
- item.subject = parsedEmail.subject || '';
- item.message = parsedEmail.body_html || parsedEmail.text || '';
- item.text = parsedEmail.text || '';
- item.attachments = parsedEmail.attachments?.map((a_item) => {
- const blob = new Blob(
- [a_item.content],
- { type: a_item.content_type || 'application/octet-stream' }
- );
- const blob_url = URL.createObjectURL(blob);
- if (a_item.content_id && a_item.content_id.length > 0) {
- item.message = item.message.replace(`cid:${a_item.content_id}`, blob_url);
- }
- return {
- id: a_item.content_id || Math.random().toString(36).substring(2, 15),
- filename: a_item.filename || a_item.content_id || "",
- size: humanFileSize(a_item.content?.length || 0),
- url: blob_url,
- blob: blob
- }
- }) || [];
- } catch (error) {
- console.log('Error parsing email with mail-parser-wasm');
- console.error(error);
- }
- if (item.subject && item.subject.length > 0 && item.message && item.message.length > 0) {
- return item;
- }
- // Fallback to PostalMime
- try {
- const parsedEmail = await PostalMime.parse(item.raw);
- item.source = parsedEmail.from.address || item.source;
- if (parsedEmail.from.address && parsedEmail.from.name) {
- item.source = `${parsedEmail.from.name} <${parsedEmail.from.address}>`;
- }
- item.subject = parsedEmail.subject || 'No Subject';
- item.message = parsedEmail.html || parsedEmail.text || item.raw;
- item.text = parsedEmail.text || '';
- item.attachments = parsedEmail.attachments?.map((a_item) => {
- const blob = new Blob(
- [a_item.content],
- { type: a_item.mimeType || 'application/octet-stream' }
- );
- const blob_url = URL.createObjectURL(blob)
- if (a_item.contentId && a_item.contentId.length > 0) {
- item.message = item.message.replace(`cid:${a_item.contentId}`, blob_url);
- }
- return {
- id: a_item.contentId || Math.random().toString(36).substring(2, 15),
- filename: a_item.filename || a_item.contentId || "",
- size: humanFileSize(a_item.content?.length || 0),
- url: blob_url,
- blob: blob
- }
- }) || [];
- } catch (error) {
- console.log('Error parsing email with PostalMime');
- console.error(error);
- item.subject = 'No Subject';
- item.message = item.raw;
- }
- return item;
-}
-
-export function getDownloadEmlUrl(raw) {
- return URL.createObjectURL(
- new Blob([raw], { type: 'text/plain' }
- ))
-}
diff --git a/cloudflare_temp_email/frontend/src/utils/index.ts b/cloudflare_temp_email/frontend/src/utils/index.ts
deleted file mode 100644
index 57400ae..0000000
--- a/cloudflare_temp_email/frontend/src/utils/index.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-export const hashPassword = async (password: string) => {
- // user crypto to hash password
- const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password));
- const hashArray = Array.from(new Uint8Array(digest));
- return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
-}
-
-export const getRouterPathWithLang = (path: string, lang: string) => {
- if (!lang || lang === 'zh') {
- return path;
- }
- return `/${lang}${path}`;
-}
-
-export const utcToLocalDate = (utcDate: string, useUTCDate: boolean) => {
- const utcDateString = `${utcDate} UTC`;
- if (useUTCDate) {
- return utcDateString;
- }
- try {
- const date = new Date(utcDateString);
- // if invalid date string
- if (isNaN(date.getTime())) return utcDateString;
-
- return date.toLocaleString();
- } catch (e) {
- console.error(e);
- }
- return utcDateString;
-}
diff --git a/cloudflare_temp_email/frontend/src/views/Admin.vue b/cloudflare_temp_email/frontend/src/views/Admin.vue
deleted file mode 100644
index 4031874..0000000
--- a/cloudflare_temp_email/frontend/src/views/Admin.vue
+++ /dev/null
@@ -1,231 +0,0 @@
-
-
-
-
-
- {{ t('accessTip') }}
-
-
-
- {{ t('ok') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/Footer.vue b/cloudflare_temp_email/frontend/src/views/Footer.vue
deleted file mode 100644
index c959038..0000000
--- a/cloudflare_temp_email/frontend/src/views/Footer.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
- {{ t('copyright') }} © 2023-{{ new Date().getFullYear() }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/Header.vue b/cloudflare_temp_email/frontend/src/views/Header.vue
deleted file mode 100644
index 489b635..0000000
--- a/cloudflare_temp_email/frontend/src/views/Header.vue
+++ /dev/null
@@ -1,306 +0,0 @@
-
-
-
-
-
-
- {{ openSettings.title || t('title') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('menu') }}
-
-
-
-
-
-
-
-
-
-
- {{ t('accessTip') }}
-
-
-
- {{ t('ok') }}
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/Index.vue b/cloudflare_temp_email/frontend/src/views/Index.vue
deleted file mode 100644
index d869bb9..0000000
--- a/cloudflare_temp_email/frontend/src/views/Index.vue
+++ /dev/null
@@ -1,191 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('enterSimpleMode') }}
-
-
-
-
-
-
-
- {{ t('query') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/User.vue b/cloudflare_temp_email/frontend/src/views/User.vue
deleted file mode 100644
index 229f027..0000000
--- a/cloudflare_temp_email/frontend/src/views/User.vue
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/Account.vue b/cloudflare_temp_email/frontend/src/views/admin/Account.vue
deleted file mode 100644
index 4d6b3cf..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/Account.vue
+++ /dev/null
@@ -1,398 +0,0 @@
-
-
-
-
-
-
- {{ t("addressCredential") }}
-
-
- {{ t("addressCredentialTip") }}
-
-
- {{ curEmailCredential }}
-
-
-
-
-
- {{ t('deleteTip') }}
-
-
- {{ t('deleteAccount') }}
-
-
-
-
- {{ t('clearInboxTip') }}
-
-
- {{ t('clearInbox') }}
-
-
-
-
- {{ t('clearSentItemsTip') }}
-
-
- {{ t('clearSentItems') }}
-
-
-
-
-
-
- {{ t('query') }}
-
-
-
-
-
-
- {{ t('itemCount') }}: {{ itemCount }}
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/AccountSettings.vue b/cloudflare_temp_email/frontend/src/views/admin/AccountSettings.vue
deleted file mode 100644
index 0dab688..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/AccountSettings.vue
+++ /dev/null
@@ -1,296 +0,0 @@
-
-
-
-
-
-
- {{ t("tip") }}
-
-
-
- {{ t('save') }}
-
-
-
-
-
-
- {{ t('manualInputPrompt') }}
-
-
-
-
-
-
-
-
- {{ t('manualInputPrompt') }}
-
-
-
-
-
-
-
-
- {{ t('manualInputPrompt') }}
-
-
-
-
-
-
-
-
- {{ t('manualInputPrompt') }}
-
-
-
-
-
-
-
-
- {{ t('manualInputPrompt') }}
-
-
-
-
-
-
-
-
- {{ t('config') }}
-
-
-
-
-
-
-
-
- {{ t('forwarding_rule_warning') }}
-
-
- {{ t('add') }}
-
-
-
- {{ t('save') }}
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/CreateAccount.vue b/cloudflare_temp_email/frontend/src/views/admin/CreateAccount.vue
deleted file mode 100644
index 3c88bcc..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/CreateAccount.vue
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
-
-
-
- {{ t('addressCredential') }}
-
- {{ result }}
-
-
-
-
-
-
-
-
-
- {{ openSettings.prefix }}
-
-
- @
-
-
-
-
- {{ t('creatNewEmail') }}
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/DatabaseManager.vue b/cloudflare_temp_email/frontend/src/views/admin/DatabaseManager.vue
deleted file mode 100644
index dc51c0e..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/DatabaseManager.vue
+++ /dev/null
@@ -1,126 +0,0 @@
-
-
-
-
-
-
-
- {{ t('need_initialization_tip') }}
-
- {{ t('init') }}
-
-
-
- {{ t('need_migration_tip') }}
-
- {{ t('migration') }}
-
-
-
-
- {{ t('current_db_version') }}: {{ dbVersionData.current_db_version || "unknown" }},
- {{ t('code_db_version') }}: {{ dbVersionData.code_db_version }}
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/MailWebhook.vue b/cloudflare_temp_email/frontend/src/views/admin/MailWebhook.vue
deleted file mode 100644
index f3abef7..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/MailWebhook.vue
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/Mails.vue b/cloudflare_temp_email/frontend/src/views/admin/Mails.vue
deleted file mode 100644
index 1c64d5d..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/Mails.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
-
-
-
-
- {{ t('query') }}
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/MailsUnknow.vue b/cloudflare_temp_email/frontend/src/views/admin/MailsUnknow.vue
deleted file mode 100644
index 064186a..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/MailsUnknow.vue
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/Maintenance.vue b/cloudflare_temp_email/frontend/src/views/admin/Maintenance.vue
deleted file mode 100644
index 6cf2c8f..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/Maintenance.vue
+++ /dev/null
@@ -1,189 +0,0 @@
-
-
-
-
-
-
-
- {{ t('cronTip') }}
-
-
-
- {{ t('save') }}
-
-
-
-
-
- {{ t('autoCleanup') }}
-
-
-
-
-
-
- {{ t('cleanupNow') }}
-
-
-
-
- {{ t('autoCleanup') }}
-
-
-
-
-
-
- {{ t('cleanupNow') }}
-
-
-
-
- {{ t('autoCleanup') }}
-
-
-
-
-
-
- {{ t('cleanupNow') }}
-
-
-
-
- {{ t('autoCleanup') }}
-
-
-
-
-
-
- {{ t('cleanupNow') }}
-
-
-
-
- {{ t('autoCleanup') }}
-
-
-
-
-
-
- {{ t('cleanupNow') }}
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/SendBox.vue b/cloudflare_temp_email/frontend/src/views/admin/SendBox.vue
deleted file mode 100644
index 14778d0..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/SendBox.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
- {{ t('query') }}
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/SendMail.vue b/cloudflare_temp_email/frontend/src/views/admin/SendMail.vue
deleted file mode 100644
index d9c59ca..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/SendMail.vue
+++ /dev/null
@@ -1,199 +0,0 @@
-
-
-
-
-
-
- {{ t('send') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ isPreview ? t('edit') : t('preview') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/SenderAccess.vue b/cloudflare_temp_email/frontend/src/views/admin/SenderAccess.vue
deleted file mode 100644
index 3513622..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/SenderAccess.vue
+++ /dev/null
@@ -1,224 +0,0 @@
-
-
-
-
-
- {{ curRow.address }}
- {{ t('modalTip') }}
-
-
- {{ t('enable') }}
-
-
-
-
-
-
-
- {{ t('ok') }}
-
-
-
-
-
-
- {{ t('query') }}
-
-
-
-
-
-
- {{ t('itemCount') }}: {{ itemCount }}
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/Statistics.vue b/cloudflare_temp_email/frontend/src/views/admin/Statistics.vue
deleted file mode 100644
index c7ea22c..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/Statistics.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/Telegram.vue b/cloudflare_temp_email/frontend/src/views/admin/Telegram.vue
deleted file mode 100644
index 9c846f6..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/Telegram.vue
+++ /dev/null
@@ -1,185 +0,0 @@
-
-
-
-
-
-
-
- {{ t('status') }}
-
-
- {{ t('init') }}
-
-
- {{ t('save') }}
-
-
-
-
-
-
- {{ t('enable') }}
-
-
-
-
- {{ t('manualInputPrompt') }}
-
-
-
-
-
-
-
-
-
- {{ t('enable') }}
-
-
-
-
- {{ t('manualInputPrompt') }}
-
-
-
-
-
-
- {{ t('globalMailPushListTip') }}
-
-
-
-
-
-
-
-
- {{ JSON.stringify(status, null, 2) }}
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/UserAddressManagement.vue b/cloudflare_temp_email/frontend/src/views/admin/UserAddressManagement.vue
deleted file mode 100644
index 8e218f9..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/UserAddressManagement.vue
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/UserManagement.vue b/cloudflare_temp_email/frontend/src/views/admin/UserManagement.vue
deleted file mode 100644
index 408ef38..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/UserManagement.vue
+++ /dev/null
@@ -1,434 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('createUser') }}
-
-
-
-
-
-
-
-
-
- {{ t('resetPassword') }}
-
-
-
-
- {{ t('deleteUserTip') }}
-
-
- {{ t('deleteUser') }}
-
-
-
-
-
- {{ t('roleDonotExist') }}
-
- {{ t('prefix') + ": " + getRolePrefix(curUserRole) }}
- {{ t('domains') + ": " + JSON.stringify(getRoleDomains(curUserRole)) }}
-
-
-
- {{ t('changeRole') }}
-
-
-
-
-
-
-
-
-
- {{ t('query') }}
-
-
-
-
-
-
- {{ t('itemCount') }}: {{ itemCount }}
-
-
-
- {{ t('createUser') }}
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/UserOauth2Settings.vue b/cloudflare_temp_email/frontend/src/views/admin/UserOauth2Settings.vue
deleted file mode 100644
index 24af487..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/UserOauth2Settings.vue
+++ /dev/null
@@ -1,274 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('addOauth2') }}
-
-
-
-
-
- {{ t("tip") }}
-
-
-
- {{ t('addOauth2') }}
-
-
- {{ t('save') }}
-
-
-
-
-
-
-
-
-
- {{ t('delete') }}
-
-
- {{ t('delete') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('enable') }}
-
-
-
-
- {{ t('manualInputPrompt') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/UserSettings.vue b/cloudflare_temp_email/frontend/src/views/admin/UserSettings.vue
deleted file mode 100644
index 0ffafd7..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/UserSettings.vue
+++ /dev/null
@@ -1,140 +0,0 @@
-
-
-
-
-
-
-
- {{ t('save') }}
-
-
-
-
-
-
-
-
-
- {{ t('enable') }}
-
-
-
-
-
-
-
- {{ t('enable') }}
-
-
-
-
- {{ t('manualInputPrompt') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/Webhook.vue b/cloudflare_temp_email/frontend/src/views/admin/Webhook.vue
deleted file mode 100644
index 0d31dc4..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/Webhook.vue
+++ /dev/null
@@ -1,107 +0,0 @@
-
-
-
-
-
-
-
- {{ t('save') }}
-
-
-
-
-
-
-
-
-
- {{ t('manualInputPrompt') }}
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/admin/WorkerConfig.vue b/cloudflare_temp_email/frontend/src/views/admin/WorkerConfig.vue
deleted file mode 100644
index a401e70..0000000
--- a/cloudflare_temp_email/frontend/src/views/admin/WorkerConfig.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
- {{ JSON.stringify(settings, null, 2) }}
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/common/About.vue b/cloudflare_temp_email/frontend/src/views/common/About.vue
deleted file mode 100644
index 003bf57..0000000
--- a/cloudflare_temp_email/frontend/src/views/common/About.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- Github
-
-
-
-
-
- Discord
-
-
-
-
-
- Telegram
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/common/AdminContact.vue b/cloudflare_temp_email/frontend/src/views/common/AdminContact.vue
deleted file mode 100644
index 517731e..0000000
--- a/cloudflare_temp_email/frontend/src/views/common/AdminContact.vue
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
- {{ t('adminContact', { msg: openSettings.adminContact }) }}
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/common/Appearance.vue b/cloudflare_temp_email/frontend/src/views/common/Appearance.vue
deleted file mode 100644
index c4650d5..0000000
--- a/cloudflare_temp_email/frontend/src/views/common/Appearance.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/common/Login.vue b/cloudflare_temp_email/frontend/src/views/common/Login.vue
deleted file mode 100644
index 77187b3..0000000
--- a/cloudflare_temp_email/frontend/src/views/common/Login.vue
+++ /dev/null
@@ -1,300 +0,0 @@
-
-
-
-
-
- {{ t('bindUserInfo') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ loginAndBindTag }}
-
-
-
-
-
- {{ t('getNewEmail') }}
-
-
-
-
-
-
-
- {{ t("getNewEmailTip1") + addressRegex.source }}
- {{ t("getNewEmailTip2") }}
- {{ t("getNewEmailTip3") }}
-
-
- {{ t('generateName') }}
-
-
-
- {{ addressPrefix }}
-
-
-
- @
-
-
-
-
-
-
-
- {{ t('getNewEmail') }}
-
-
-
-
-
-
- {{ t('pleaseGetNewEmail') }}
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/index/AccountSettings.vue b/cloudflare_temp_email/frontend/src/views/index/AccountSettings.vue
deleted file mode 100644
index 14dceee..0000000
--- a/cloudflare_temp_email/frontend/src/views/index/AccountSettings.vue
+++ /dev/null
@@ -1,169 +0,0 @@
-
-
-
-
-
-
- {{ t('showAddressCredential') }}
-
-
- {{ t('clearInbox') }}
-
-
- {{ t('clearSentItems') }}
-
-
- {{ t('logout') }}
-
-
- {{ t('deleteAccount') }}
-
-
-
- {{ t('logoutConfirm') }}
-
-
- {{ t('logout') }}
-
-
-
-
- {{ t('deleteAccountConfirm') }}
-
-
- {{ t('deleteAccount') }}
-
-
-
-
- {{ t('clearInboxConfirm') }}
-
-
- {{ t('clearInbox') }}
-
-
-
-
- {{ t('clearSentItemsConfirm') }}
-
-
- {{ t('clearSentItems') }}
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/index/AddressBar.vue b/cloudflare_temp_email/frontend/src/views/index/AddressBar.vue
deleted file mode 100644
index c88c542..0000000
--- a/cloudflare_temp_email/frontend/src/views/index/AddressBar.vue
+++ /dev/null
@@ -1,183 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- {{ addressLabel }}
-
- {{ t('addressManage') }}
-
-
- {{ t('changeAddress') }}
-
-
- {{ t('addressManage') }}
-
-
- {{ t('copy') }}
-
-
-
-
-
-
-
-
-
-
- {{ t('fetchAddressError') }}
-
-
-
-
-
-
-
- {{ t('userLogin') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t("addressCredentialTip") }}
-
-
- {{ jwt }}
-
-
-
-
-
- {{ getUrlWithJwt() }}
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/index/Attachment.vue b/cloudflare_temp_email/frontend/src/views/index/Attachment.vue
deleted file mode 100644
index 7327537..0000000
--- a/cloudflare_temp_email/frontend/src/views/index/Attachment.vue
+++ /dev/null
@@ -1,126 +0,0 @@
-
-
-
-
-
- {{ curRow.key }}
-
- {{ t('download') }}
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/index/AutoReply.vue b/cloudflare_temp_email/frontend/src/views/index/AutoReply.vue
deleted file mode 100644
index af4aafc..0000000
--- a/cloudflare_temp_email/frontend/src/views/index/AutoReply.vue
+++ /dev/null
@@ -1,136 +0,0 @@
-
-
-
-
-
-
- {{ t('save') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/index/LocalAddress.vue b/cloudflare_temp_email/frontend/src/views/index/LocalAddress.vue
deleted file mode 100644
index 8e75b60..0000000
--- a/cloudflare_temp_email/frontend/src/views/index/LocalAddress.vue
+++ /dev/null
@@ -1,159 +0,0 @@
-
-
-
-
-
- {{ t('tip') }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/index/SendMail.vue b/cloudflare_temp_email/frontend/src/views/index/SendMail.vue
deleted file mode 100644
index 3aaeff0..0000000
--- a/cloudflare_temp_email/frontend/src/views/index/SendMail.vue
+++ /dev/null
@@ -1,237 +0,0 @@
-
-
-
-
-
-
-
- {{ t('requestAccessTip') }}
- {{ t('requestAccess')
- }}
-
-
-
-
-
- {{ t('send_balance') }}: {{ settings.send_balance }}
-
-
- {{ t('send') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ isPreview ? t('edit') : t('preview') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/index/SimpleIndex.vue b/cloudflare_temp_email/frontend/src/views/index/SimpleIndex.vue
deleted file mode 100644
index 48b89eb..0000000
--- a/cloudflare_temp_email/frontend/src/views/index/SimpleIndex.vue
+++ /dev/null
@@ -1,283 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ settings.address }}
-
-
-
-
-
-
-
-
- {{ t('refreshMails') }}
-
-
-
-
-
-
-
- {{ t('copyAddress') }}
-
-
-
-
-
-
-
- {{ t('exitSimpleIndex') }}
-
-
-
-
-
-
-
- {{ t('accountSettings') }}
-
-
-
-
- {{ t('refreshAfter', { msg: Math.max(0, currentAutoRefreshInterval) }) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('prevPage') }}
-
-
- {{ t('mailCount', { current: currentPageDisplay, total: totalCount }) }}
-
-
-
-
-
-
-
- {{ t('nextPage') }}
-
-
-
-
-
-
-
-
-
{{ currentMail.subject }}
-
-
-
-
-
-
-
-
- {{ t("addressCredentialTip") }}
-
-
- {{ jwt }}
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/index/TelegramAddress.vue b/cloudflare_temp_email/frontend/src/views/index/TelegramAddress.vue
deleted file mode 100644
index fdcc15c..0000000
--- a/cloudflare_temp_email/frontend/src/views/index/TelegramAddress.vue
+++ /dev/null
@@ -1,158 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/index/Webhook.vue b/cloudflare_temp_email/frontend/src/views/index/Webhook.vue
deleted file mode 100644
index d07c5f9..0000000
--- a/cloudflare_temp_email/frontend/src/views/index/Webhook.vue
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/telegram/Mail.vue b/cloudflare_temp_email/frontend/src/views/telegram/Mail.vue
deleted file mode 100644
index 92ecb64..0000000
--- a/cloudflare_temp_email/frontend/src/views/telegram/Mail.vue
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
- ID: {{ curMail.id }}
-
-
- Date: {{ utcToLocalDate(curMail.created_at, useUTCDate) }}
-
-
- FROM: {{ curMail.source }}
-
-
- TO: {{ curMail.address }}
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/user/AddressManagement.vue b/cloudflare_temp_email/frontend/src/views/user/AddressManagement.vue
deleted file mode 100644
index 6593666..0000000
--- a/cloudflare_temp_email/frontend/src/views/user/AddressManagement.vue
+++ /dev/null
@@ -1,247 +0,0 @@
-
-
-
-
-
-
- {{ t("transferAddressTip") }}
- {{ t('transferAddress') + ": " + currentAddress }}
-
-
-
-
- {{ t('transferAddress') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/user/BindAddress.vue b/cloudflare_temp_email/frontend/src/views/user/BindAddress.vue
deleted file mode 100644
index 42a1d57..0000000
--- a/cloudflare_temp_email/frontend/src/views/user/BindAddress.vue
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/user/UserBar.vue b/cloudflare_temp_email/frontend/src/views/user/UserBar.vue
deleted file mode 100644
index 54986dc..0000000
--- a/cloudflare_temp_email/frontend/src/views/user/UserBar.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- {{ t('currentUser') }} {{ userSettings.user_email }}
-
-
-
-
-
-
- {{ t('fetchUserSettingsError') }}
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/user/UserLogin.vue b/cloudflare_temp_email/frontend/src/views/user/UserLogin.vue
deleted file mode 100644
index da4f5bd..0000000
--- a/cloudflare_temp_email/frontend/src/views/user/UserLogin.vue
+++ /dev/null
@@ -1,308 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('login') }}
-
-
- {{ t('forgotPassword') }}
-
-
-
-
-
-
- {{ t('loginWithPasskey') }}
-
-
- {{ t('loginWith', { provider: item.name }) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ verifyCodeTimeout > 0 ? t('waitforVerifyCode', { timeout: verifyCodeTimeout })
- : t('sendVerificationCode') }}
-
-
-
-
-
- {{ t('register') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ verifyCodeTimeout > 0 ? t('waitforVerifyCode', { timeout: verifyCodeTimeout })
- : t('sendVerificationCode') }}
-
-
-
-
- {{ t('resetPassword') }}
-
-
-
-
- {{ t('cannotForgotPassword') }}
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/user/UserMailBox.vue b/cloudflare_temp_email/frontend/src/views/user/UserMailBox.vue
deleted file mode 100644
index 6926966..0000000
--- a/cloudflare_temp_email/frontend/src/views/user/UserMailBox.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
-
-
-
-
-
- {{ t('query') }}
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/user/UserOauth2Callback.vue b/cloudflare_temp_email/frontend/src/views/user/UserOauth2Callback.vue
deleted file mode 100644
index 7fe81f8..0000000
--- a/cloudflare_temp_email/frontend/src/views/user/UserOauth2Callback.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/src/views/user/UserSettings.vue b/cloudflare_temp_email/frontend/src/views/user/UserSettings.vue
deleted file mode 100644
index 805646a..0000000
--- a/cloudflare_temp_email/frontend/src/views/user/UserSettings.vue
+++ /dev/null
@@ -1,259 +0,0 @@
-
-
-
-
-
-
- {{ t('showPasskeyList') }}
-
-
- {{ t('createPasskey') }}
-
-
-
- {{ t('passordTip') }}
-
-
-
- {{ t('logout') }}
-
-
-
-
-
-
- {{ t('createPasskey') }}
-
-
-
-
-
-
-
- {{ t('renamePasskey') }}
-
-
-
-
-
-
-
- {{ t('logoutConfirm') }}
-
-
- {{ t('logout') }}
-
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/frontend/tsconfig.json b/cloudflare_temp_email/frontend/tsconfig.json
deleted file mode 100644
index f2e5e29..0000000
--- a/cloudflare_temp_email/frontend/tsconfig.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "compilerOptions": {
- "target": "ESNext",
- "module": "ESNext",
- "moduleResolution": "Bundler",
- "strict": true,
- "skipLibCheck": true,
- "lib": [
- "ESNext"
- ],
- "types": []
- },
-}
diff --git a/cloudflare_temp_email/frontend/vite.config.js b/cloudflare_temp_email/frontend/vite.config.js
deleted file mode 100644
index 0fa8216..0000000
--- a/cloudflare_temp_email/frontend/vite.config.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import { fileURLToPath, URL } from 'node:url'
-
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
-import { VitePWA } from 'vite-plugin-pwa'
-import AutoImport from 'unplugin-auto-import/vite'
-import Components from 'unplugin-vue-components/vite'
-import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
-import wasm from "vite-plugin-wasm";
-import topLevelAwait from "vite-plugin-top-level-await";
-
-// https://vitejs.dev/config/
-export default defineConfig({
- build: {
- outDir: './dist',
- },
- plugins: [
- vue(),
- wasm(),
- topLevelAwait(),
- AutoImport({
- imports: [
- 'vue',
- {
- 'naive-ui': [
- 'useMessage',
- 'useNotification',
- 'NButton',
- 'NPopconfirm',
- 'NIcon',
- ]
- }
- ]
- }),
- Components({
- resolvers: [NaiveUiResolver()]
- }),
- VitePWA({
- registerType: null,
- devOptions: {
- enabled: false
- },
- workbox: {
- disableDevLogs: true,
- globPatterns: [],
- runtimeCaching: [],
- navigateFallback: null,
- cleanupOutdatedCaches: true,
- },
- manifest: {
- name: 'Temp Email',
- short_name: 'Temp Email',
- description: 'Temp Email - Temporary Email',
- theme_color: '#ffffff',
- icons: [
- {
- src: '/logo.png',
- sizes: '192x192',
- type: 'image/png'
- }
- ]
- }
- })
- ],
- resolve: {
- alias: {
- '@': fileURLToPath(new URL('./src', import.meta.url))
- }
- },
- define: {
- 'import.meta.env.PACKAGE_VERSION': JSON.stringify(process.env.npm_package_version),
- },
- esbuild: {
- supported: {
- 'top-level-await': true
- },
- }
-})
diff --git a/cloudflare_temp_email/mail-parser-wasm/.gitignore b/cloudflare_temp_email/mail-parser-wasm/.gitignore
deleted file mode 100644
index f541fb5..0000000
--- a/cloudflare_temp_email/mail-parser-wasm/.gitignore
+++ /dev/null
@@ -1,15 +0,0 @@
-# Generated by Cargo
-# will have compiled files and executables
-debug/
-target/
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
-Cargo.lock
-
-# These are backup files generated by rustfmt
-**/*.rs.bk
-
-# MSVC Windows builds of rustc generate these, which store debugging information
-*.pdb
-web/
diff --git a/cloudflare_temp_email/mail-parser-wasm/Cargo.toml b/cloudflare_temp_email/mail-parser-wasm/Cargo.toml
deleted file mode 100644
index 07f5652..0000000
--- a/cloudflare_temp_email/mail-parser-wasm/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[package]
-name = "mail-parser-wasm"
-version = "0.2.1"
-edition = "2021"
-description = "A simple mail parser for wasm"
-license = "MIT"
-
-[lib]
-crate-type = ["cdylib"]
-
-[dependencies]
-mail-parser = "0.9.4"
-wasm-bindgen = "0.2.99"
diff --git a/cloudflare_temp_email/mail-parser-wasm/README.md b/cloudflare_temp_email/mail-parser-wasm/README.md
deleted file mode 100644
index a72aeb3..0000000
--- a/cloudflare_temp_email/mail-parser-wasm/README.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# mail-parser-wasm web and cf worker
-
-## [mail-parser-wasm](https://www.npmjs.com/package/mail-parser-wasm)
-
-### mail-parser-wasm usage
-
-```bash
-pnpm add mail-parser-wasm
-```
-
-```js
-import { parse_message } from 'mail-parser-wasm'
-
-const parsedEmail = parse_message(rawEmail);
-```
-
-### mail-parser-wasm build
-
-```bash
-wasm-pack build --release
-wasm-pack publish
-```
-
-## [mail-parser-wasm-worker](https://www.npmjs.com/package/mail-parser-wasm-worker)
-
-### mail-parser-wasm-worker usage
-
-```bash
-pnpm add mail-parser-wasm-worker
-```
-
-```js
-import { parse_message_wrapper } from 'mail-parser-wasm-worker'
-
-const parsedEmail = parse_message_wrapper(rawEmail);
-```
-
-### mail-parser-wasm-worker build
-
-```bash
-wasm-pack build --out-dir web --target web --release
-find web/ -type f ! -name '*.json' ! -name '.gitignore' -exec cp {} worker/ \;
-# modify worker/package.json version or whatever
-pnpm publish worker --no-git-checks
-```
diff --git a/cloudflare_temp_email/mail-parser-wasm/src/lib.rs b/cloudflare_temp_email/mail-parser-wasm/src/lib.rs
deleted file mode 100644
index e10b864..0000000
--- a/cloudflare_temp_email/mail-parser-wasm/src/lib.rs
+++ /dev/null
@@ -1,194 +0,0 @@
-extern crate wasm_bindgen;
-
-use mail_parser::{MessageParser, MimeHeaders};
-use wasm_bindgen::prelude::*;
-
-#[derive(Clone)]
-#[wasm_bindgen]
-pub struct AttachmentResult {
- content_id: String,
- content_type: String,
- filename: String,
- content: Vec,
-}
-
-#[wasm_bindgen]
-impl AttachmentResult {
- #[wasm_bindgen(getter)]
- pub fn content_id(&self) -> String {
- self.content_id.clone()
- }
-
- #[wasm_bindgen(getter)]
- pub fn content_type(&self) -> String {
- self.content_type.clone()
- }
-
- #[wasm_bindgen(getter)]
- pub fn filename(&self) -> String {
- self.filename.clone()
- }
-
- #[wasm_bindgen(getter)]
- pub fn content(&self) -> Vec {
- self.content.clone()
- }
-}
-
-#[derive(Clone)]
-#[wasm_bindgen]
-pub struct MessageHeader {
- key: String,
- value: String,
-}
-
-#[wasm_bindgen]
-impl MessageHeader {
- #[wasm_bindgen(getter)]
- pub fn key(&self) -> String {
- self.key.clone()
- }
-
- #[wasm_bindgen(getter)]
- pub fn value(&self) -> String {
- self.value.clone()
- }
-}
-
-#[wasm_bindgen]
-pub struct MessageResult {
- sender: String,
- subject: String,
- headers: Vec,
- body_html: String,
- text: String,
- attachments: Vec,
-}
-
-#[wasm_bindgen]
-impl MessageResult {
- #[wasm_bindgen(getter)]
- pub fn sender(&self) -> String {
- self.sender.clone()
- }
-
- #[wasm_bindgen(getter)]
- pub fn subject(&self) -> String {
- self.subject.clone()
- }
-
- #[wasm_bindgen(getter)]
- pub fn headers(&self) -> Vec {
- self.headers.clone()
- }
-
- #[wasm_bindgen(getter)]
- pub fn body_html(&self) -> String {
- self.body_html.clone()
- }
-
- #[wasm_bindgen(getter)]
- pub fn text(&self) -> String {
- self.text.clone()
- }
-
- #[wasm_bindgen(getter)]
- pub fn attachments(&self) -> Vec {
- self.attachments.clone()
- }
-}
-
-pub fn parse_attachment(message: &mail_parser::Message) -> Vec {
- let mut attachments: Vec = Vec::new();
- for attachment in message.attachments() {
- if !attachment.is_message() {
- attachments.push(AttachmentResult {
- content_id: attachment
- .content_id()
- .map(|id| id.to_owned())
- .unwrap_or(String::new()),
- content_type: attachment
- .content_type()
- .map(|ct| {
- let c_type = ct.c_type.clone().into_owned();
- let c_subtype = ct.c_subtype.clone();
- if c_subtype.is_none() {
- return c_type;
- } else {
- return format!("{}/{}", c_type, c_subtype.unwrap());
- }
- })
- .unwrap_or(String::new()),
- filename: attachment
- .attachment_name()
- .map(|name| name.to_owned())
- .unwrap_or(String::new()),
- content: attachment.contents().to_vec(),
- });
- } else {
- attachments.append(
- &mut attachment
- .message()
- .map(|msg| parse_attachment(msg))
- .unwrap_or(Vec::new()),
- );
- }
- }
- attachments
-}
-
-#[wasm_bindgen]
-pub fn parse_message(raw_message: &str) -> MessageResult {
- // check if the message is valid
- let res = MessageParser::default().parse(raw_message);
- if res.is_none() {
- return MessageResult {
- sender: String::new(),
- subject: String::new(),
- headers: Vec::new(),
- body_html: String::new(),
- text: String::new(),
- attachments: Vec::new(),
- };
- }
- let message = res.unwrap();
-
- MessageResult {
- sender: message
- .from()
- .and_then(|from| from.first())
- .map(|addr| {
- if addr.name().is_some() {
- return format!(
- "{} <{}>",
- addr.name().unwrap(),
- addr.address().unwrap_or("")
- );
- } else {
- return addr.address().unwrap_or("").to_owned();
- }
- })
- .unwrap_or(String::new()),
- subject: message
- .subject()
- .map(|subject| subject.to_owned())
- .unwrap_or(String::new()),
- headers: message
- .headers()
- .iter()
- .map(|header| MessageHeader {
- key: header.name().to_owned(),
- value: header.value().as_text().unwrap_or("").to_owned(),
- })
- .collect(),
- body_html: message
- .body_html(0)
- .map(|html| html.into_owned())
- .unwrap_or(String::new()),
- text: message
- .body_text(0)
- .map(|text| text.into_owned())
- .unwrap_or(String::new()),
- attachments: parse_attachment(&message),
- }
-}
diff --git a/cloudflare_temp_email/mail-parser-wasm/worker/index.d.ts b/cloudflare_temp_email/mail-parser-wasm/worker/index.d.ts
deleted file mode 100644
index 286ee41..0000000
--- a/cloudflare_temp_email/mail-parser-wasm/worker/index.d.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import initAsync, { MessageResult } from './mail_parser_wasm';
-import MODULE from './mail_parser_wasm_bg.wasm';
-export { initAsync, MODULE };
-export * from './mail_parser_wasm';
-/**
-* @param {string} raw_message
-* @returns {MessageResult}
-*/
-export function parse_message_wrapper(raw_message: string): MessageResult;
diff --git a/cloudflare_temp_email/mail-parser-wasm/worker/index.js b/cloudflare_temp_email/mail-parser-wasm/worker/index.js
deleted file mode 100644
index 96500c5..0000000
--- a/cloudflare_temp_email/mail-parser-wasm/worker/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import initAsync, { initSync, parse_message } from './mail_parser_wasm';
-import MODULE from './mail_parser_wasm_bg.wasm';
-
-initSync({ module: MODULE });
-
-
-export { initAsync, MODULE };
-export * from './mail_parser_wasm';
-export const parse_message_wrapper = (raw_message) => {
- initSync({ module: MODULE });
- return parse_message(raw_message);
-}
diff --git a/cloudflare_temp_email/mail-parser-wasm/worker/package.json b/cloudflare_temp_email/mail-parser-wasm/worker/package.json
deleted file mode 100644
index 433f77a..0000000
--- a/cloudflare_temp_email/mail-parser-wasm/worker/package.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "name": "mail-parser-wasm-worker",
- "description": "A simple mail parser for worker",
- "homepage": "https://github.com/dreamhunter2333/cloudflare_temp_email/tree/main/mail-parser-wasm",
- "repository": {
- "type": "git",
- "url": "https://github.com/dreamhunter2333/cloudflare_temp_email",
- "directory": "mail-parser-wasm"
- },
- "version": "0.2.1",
- "license": "MIT",
- "files": [
- "mail_parser_wasm_bg.wasm",
- "mail_parser_wasm.js",
- "mail_parser_wasm.d.ts",
- "index.js",
- "index.d.ts"
- ],
- "module": "index.js",
- "types": "index.d.ts",
- "sideEffects": [
- "./snippets/*"
- ]
-}
diff --git a/cloudflare_temp_email/pages/.gitignore b/cloudflare_temp_email/pages/.gitignore
deleted file mode 100644
index 1cdac82..0000000
--- a/cloudflare_temp_email/pages/.gitignore
+++ /dev/null
@@ -1,34 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
-
-node_modules
-.DS_Store
-dist
-dist-ssr
-coverage
-*.local
-
-/cypress/videos/
-/cypress/screenshots/
-
-# Editor directories and files
-.vscode/*
-!.vscode/extensions.json
-.idea
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
-
-.env.*
-*-dist/
-components.d.ts
-.wrangler/
-pnpm-lock.yaml
diff --git a/cloudflare_temp_email/pages/functions/_middleware.js b/cloudflare_temp_email/pages/functions/_middleware.js
deleted file mode 100644
index a95df43..0000000
--- a/cloudflare_temp_email/pages/functions/_middleware.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const API_PATHS = [
- "/api/",
- "/open_api/",
- "/user_api/",
- "/admin/",
- "/telegram/",
- "/external/",
-];
-
-export async function onRequest(context) {
- const reqPath = new URL(context.request.url).pathname;
- if (API_PATHS.map(path => reqPath.startsWith(path)).some(Boolean)) {
- return context.env.BACKEND.fetch(context.request);
- }
- return await context.next();
-}
diff --git a/cloudflare_temp_email/pages/package.json b/cloudflare_temp_email/pages/package.json
deleted file mode 100644
index c784373..0000000
--- a/cloudflare_temp_email/pages/package.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "temp-email-pages",
- "version": "1.0.5",
- "description": "",
- "main": "index.js",
- "scripts": {
- "dev": "wrangler pages dev",
- "deploy": "wrangler pages deploy --branch production"
- },
- "keywords": [],
- "author": "",
- "license": "ISC",
- "devDependencies": {
- "wrangler": "^4.34.0"
- },
- "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
-}
diff --git a/cloudflare_temp_email/scripts/update-dependencies.sh b/cloudflare_temp_email/scripts/update-dependencies.sh
deleted file mode 100755
index 4eae50f..0000000
--- a/cloudflare_temp_email/scripts/update-dependencies.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-cd frontend/
-pnpm up
-pnpm add -D wrangler@latest
-cd ..
-
-cd worker/
-pnpm up
-pnpm add -D wrangler@latest
-cd ..
-
-cd pages/
-pnpm up
-pnpm add -D wrangler@latest
-cd ..
-
-cd vitepress-docs/
-pnpm up --latest
-pnpm add -D wrangler@latest
-cd ..
diff --git a/cloudflare_temp_email/smtp_proxy_server/.env.example b/cloudflare_temp_email/smtp_proxy_server/.env.example
deleted file mode 100644
index b683f38..0000000
--- a/cloudflare_temp_email/smtp_proxy_server/.env.example
+++ /dev/null
@@ -1,2 +0,0 @@
-proxy_url=https://temp-email-api.xxx.xxx
-port=8025
diff --git a/cloudflare_temp_email/smtp_proxy_server/.gitignore b/cloudflare_temp_email/smtp_proxy_server/.gitignore
deleted file mode 100644
index c7cdace..0000000
--- a/cloudflare_temp_email/smtp_proxy_server/.gitignore
+++ /dev/null
@@ -1,161 +0,0 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-cover/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-.pybuilder/
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-# For a library or package, you might want to ignore these files since the code is
-# intended to run in multiple environments; otherwise, check them in:
-# .python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# poetry
-# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
-# This is especially recommended for binary packages to ensure reproducibility, and is more
-# commonly ignored for libraries.
-# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
-#poetry.lock
-
-# pdm
-# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
-#pdm.lock
-# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
-# in version control.
-# https://pdm.fming.dev/#use-with-ide
-.pdm.toml
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# pytype static type analyzer
-.pytype/
-
-# Cython debug symbols
-cython_debug/
-
-# PyCharm
-# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
-# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
-# and can be added to the global gitignore or merged into this file. For a more nuclear
-# option (not recommended) you can uncomment the following to ignore the entire idea folder.
-#.idea/
-test*
diff --git a/cloudflare_temp_email/smtp_proxy_server/config.py b/cloudflare_temp_email/smtp_proxy_server/config.py
deleted file mode 100644
index 0c6b933..0000000
--- a/cloudflare_temp_email/smtp_proxy_server/config.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import logging
-from pydantic_settings import BaseSettings
-
-logging.basicConfig(
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
-)
-
-_logger = logging.getLogger(__name__)
-_logger.setLevel(logging.INFO)
-
-
-class Settings(BaseSettings):
- proxy_url: str = "http://localhost:8787"
- port: int = 8025
- imap_port: int = 11143
- basic_password: str = ""
-
- class Config:
- env_file = ".env"
-
-
-settings = Settings()
diff --git a/cloudflare_temp_email/smtp_proxy_server/docker-compose.yaml b/cloudflare_temp_email/smtp_proxy_server/docker-compose.yaml
deleted file mode 100644
index 4b753cd..0000000
--- a/cloudflare_temp_email/smtp_proxy_server/docker-compose.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-services:
- smtp_proxy_server:
- image: ghcr.io/dreamhunter2333/cloudflare_temp_email/smtp_proxy_server:latest
- # build:
- # context: .
- # dockerfile: dockerfile
- container_name: "smtp_proxy_server"
- ports:
- - "8025:8025"
- - "11143:11143"
- environment:
- - proxy_url=https://temp-email-api.xxx.xxx
- - port=8025
- - imap_port=11143
diff --git a/cloudflare_temp_email/smtp_proxy_server/dockerfile b/cloudflare_temp_email/smtp_proxy_server/dockerfile
deleted file mode 100644
index 425490a..0000000
--- a/cloudflare_temp_email/smtp_proxy_server/dockerfile
+++ /dev/null
@@ -1,7 +0,0 @@
-FROM python:3.12-slim
-
-WORKDIR /app
-COPY requirements.txt /requirements.txt
-RUN python3 -m pip install -r /requirements.txt
-COPY . /app
-ENTRYPOINT [ "python3", "main.py" ]
diff --git a/cloudflare_temp_email/smtp_proxy_server/imap_server.py b/cloudflare_temp_email/smtp_proxy_server/imap_server.py
deleted file mode 100644
index 93ae36d..0000000
--- a/cloudflare_temp_email/smtp_proxy_server/imap_server.py
+++ /dev/null
@@ -1,314 +0,0 @@
-import json
-import logging
-import httpx
-
-from io import BytesIO
-from twisted.mail import imap4
-from zope.interface import implementer
-from twisted.cred.portal import Portal, IRealm
-from twisted.internet import protocol, reactor, defer
-from twisted.cred.checkers import ICredentialsChecker, IUsernamePassword
-
-from config import settings
-from parse_email import generate_email_model, parse_email
-from models import EmailModel
-
-_logger = logging.getLogger(__name__)
-_logger.setLevel(logging.INFO)
-
-
-@implementer(imap4.IMessage)
-class SimpleMessage:
-
- def __init__(self, uid=None, email_model: EmailModel = None):
- self.uid = uid
- self.email = email_model
- self.subparts = self.email.subparts
-
- def getUID(self):
- return self.uid
-
- def getHeaders(self, negate, *names):
- self.got_headers = negate, names
- return {
- k.lower(): v
- for k, v in self.email.headers.items()
- }
-
- def isMultipart(self):
- return len(self.subparts) > 0
-
- def getSubPart(self, part):
- self.got_subpart = part
- return SimpleMessage(email_model=self.subparts[part])
-
- def getBodyFile(self):
- return BytesIO(self.email.body.encode("utf-8"))
-
- def getSize(self):
- return self.email.size
-
- def getFlags(self):
- return ["\\Seen"]
-
- def getInternalDate(self):
- return self.email.headers.get("Date", "Mon, 1 Jan 1900 00:00:00 +0000")
-
-
-@implementer(imap4.IMailboxInfo, imap4.IMailbox)
-class SimpleMailbox:
-
- def __init__(self, name, password):
- self.name = name
- self.password = password
- self.listeners = []
- self.addListener = self.listeners.append
- self.removeListener = self.listeners.remove
- self.message_count = 0
- self._update_message_count()
-
- def _update_message_count(self):
- """主动获取邮件总数"""
- try:
- if self.name == "INBOX":
- endpoint = "/api/mails"
- elif self.name == "SENT":
- endpoint = "/api/sendbox"
- else:
- return
-
- res = httpx.get(
- f"{settings.proxy_url}{endpoint}?limit=1&offset=0",
- headers={
- "Authorization": f"Bearer {self.password}",
- "x-custom-auth": f"{settings.basic_password}",
- "Content-Type": "application/json"
- }
- )
- if res.status_code == 200:
- self.message_count = res.json()["count"]
- # _logger.info(f"Updated {self.name} message count: {self.message_count}")
- except Exception as e:
- _logger.error(f"Failed to update message count for {self.name}: {e}")
-
- def getFlags(self):
- return ["\\Seen"]
-
- def getUIDValidity(self):
- return 0
-
- def getMessageCount(self):
- # 每次请求时更新邮件总数
- self._update_message_count()
- return self.message_count
-
- def getRecentCount(self):
- return 0
-
- def getUnseenCount(self):
- return 0
-
- def isWriteable(self):
- return 0
-
- def destroy(self):
- pass
-
- def getHierarchicalDelimiter(self):
- return "/"
-
- def requestStatus(self, names):
- # 在状态请求时也更新邮件总数
- self._update_message_count()
- r = {}
- if "MESSAGES" in names:
- r["MESSAGES"] = self.getMessageCount()
- if "RECENT" in names:
- r["RECENT"] = self.getRecentCount()
- if "UIDNEXT" in names:
- r["UIDNEXT"] = self.getMessageCount() + 1
- if "UIDVALIDITY" in names:
- r["UIDVALIDITY"] = self.getUIDValidity()
- if "UNSEEN" in names:
- r["UNSEEN"] = self.getUnseenCount()
- return defer.succeed(r)
-
- def fetch(self, messages, uid):
- """边查边返回邮件"""
- def email_generator():
- for range_item in messages.ranges:
- start, end = range_item
- _logger.info(f"Fetching messages: {self.name}, range: {start}-{end}")
-
- for email_data in self.fetchGenerator(start, end):
- yield email_data
-
- # 返回生成器,让IMAP4服务器逐个处理
- return email_generator()
-
- def fetchGenerator(self, start, end):
- """通用的邮件获取生成器,边查边返回"""
- start = max(start, 1)
-
- # 根据邮箱类型确定API端点
- if self.name == "INBOX":
- endpoint = "/api/mails"
- elif self.name == "SENT":
- endpoint = "/api/sendbox"
- else:
- return
-
- # 首先获取服务端邮件总数
- count_res = httpx.get(
- f"{settings.proxy_url}{endpoint}?limit=1&offset=0",
- headers={
- "Authorization": f"Bearer {self.password}",
- "x-custom-auth": f"{settings.basic_password}",
- "Content-Type": "application/json"
- }
- )
- if count_res.status_code != 200:
- _logger.error(
- f"Failed to get {self.name} email count: "
- f"code=[{count_res.status_code}] text=[{count_res.text}]"
- )
- return
-
- total_count = count_res.json()["count"]
- self.message_count = total_count
-
- if total_count == 0 or start > total_count:
- return
-
- # 分批处理,每次获取一小批就立即返回
- batch_size = 20
- current_start = start
- current_end = min(end or total_count, total_count)
-
- while current_start <= current_end:
- batch_end = min(current_start + batch_size - 1, current_end)
-
- # 计算这一批的参数
- limit = batch_end - current_start + 1
- server_offset = total_count - batch_end
- server_offset = max(0, server_offset)
-
- _logger.info(
- f"Fetching batch: start={current_start}, end={batch_end}, "
- f"total_count={total_count}, limit={limit}, "
- f"server_offset={server_offset}"
- )
-
- res = httpx.get(
- f"{settings.proxy_url}{endpoint}?limit={limit}&offset={server_offset}",
- headers={
- "Authorization": f"Bearer {self.password}",
- "x-custom-auth": f"{settings.basic_password}",
- "Content-Type": "application/json"
- }
- )
- if res.status_code != 200:
- _logger.error(
- f"Failed to fetch {self.name} emails: "
- f"code=[{res.status_code}] text=[{res.text}]"
- )
- break
-
- emails = res.json()["results"]
- for i, item in enumerate(reversed(emails)):
- uid = total_count - server_offset - len(emails) + i + 1
- if current_start <= uid <= batch_end:
- if self.name == "INBOX":
- email_model = parse_email(item["raw"])
- elif self.name == "SENT":
- email_model = generate_email_model(item)
-
- # 立即返回这封邮件
- yield (uid, SimpleMessage(uid, email_model))
-
- current_start = batch_end + 1
-
- def getUID(self, message):
- return message.uid
-
- def store(self, messages, flags, mode, uid):
- # IMailboxIMAP.store
- raise NotImplementedError
-
-
-class Account(imap4.MemoryAccount):
-
- def __init__(self, user, password):
- self.password = password
- super().__init__(user)
-
- def isSubscribed(self, name):
- return name.upper() in ["INBOX", "SENT"]
-
- def _emptyMailbox(self, name, id):
- _logger.info(f"New mailbox: {name}, {id}")
- if name == "INBOX":
- return SimpleMailbox(name, self.password)
- if name == "SENT":
- return SimpleMailbox(name, self.password)
- raise imap4.NoSuchMailbox(name.encode("utf-8"))
-
- def select(self, name, rw=1):
- return imap4.MemoryAccount.select(self, name)
-
-
-class SimpleIMAPServer(imap4.IMAP4Server):
- def __init__(self, factory):
- imap4.IMAP4Server.__init__(self)
- self.factory = factory
-
- def lineReceived(self, line):
- # _logger.info(f"Received: {line}")
- super().lineReceived(line)
-
- def sendLine(self, line):
- # _logger.info(f"Sent: {line}")
- super().sendLine(line)
-
-
-@implementer(IRealm)
-class SimpleRealm:
- def requestAvatar(self, avatarId, mind, *interfaces):
- res = json.loads(avatarId)
- account = Account(res["username"], res["password"])
- account.addMailbox("INBOX")
- account.addMailbox("SENT")
- return imap4.IAccount, account, lambda: None
-
-
-class IMAPFactory(protocol.Factory):
- def __init__(self, portal):
- self.portal = portal
-
- def buildProtocol(self, addr):
- p = SimpleIMAPServer(self)
- p.portal = self.portal
- return p
-
-
-@implementer(ICredentialsChecker)
-class CustomChecker:
- credentialInterfaces = (IUsernamePassword,)
-
- def requestAvatarId(self, credentials):
- return defer.succeed(json.dumps({
- "username": credentials.username.decode(),
- "password": credentials.password.decode(),
- }))
-
-
-def start_imap_server():
- _logger.info(f"Starting IMAP server on port {settings.imap_port}")
- portal = Portal(SimpleRealm(), [CustomChecker()])
- reactor.listenTCP(settings.imap_port, IMAPFactory(portal))
- reactor.run()
-
-
-if __name__ == "__main__":
- _logger.info(f"Starting server settings[{settings}]")
- start_imap_server()
diff --git a/cloudflare_temp_email/smtp_proxy_server/main.py b/cloudflare_temp_email/smtp_proxy_server/main.py
deleted file mode 100644
index 5f587fc..0000000
--- a/cloudflare_temp_email/smtp_proxy_server/main.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import logging
-import multiprocessing
-
-from smtp_server import start_smtp_server
-from imap_server import start_imap_server
-from config import settings
-
-_logger = logging.getLogger(__name__)
-_logger.setLevel(logging.INFO)
-
-if __name__ == '__main__':
- _logger.info(f"Starting server settings[{settings}]")
- process_list = [
- multiprocessing.Process(target=start_smtp_server, args=()),
- multiprocessing.Process(target=start_imap_server, args=()),
- ]
- try:
- for p in process_list:
- p.start()
- for p in process_list:
- p.join()
- except KeyboardInterrupt:
- for p in process_list:
- p.terminate()
diff --git a/cloudflare_temp_email/smtp_proxy_server/models.py b/cloudflare_temp_email/smtp_proxy_server/models.py
deleted file mode 100644
index 4791e5e..0000000
--- a/cloudflare_temp_email/smtp_proxy_server/models.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from typing import Dict, List
-from pydantic import BaseModel
-
-
-class EmailModel(BaseModel):
- headers: Dict[str, str]
- body: str
- content_type: str
- subparts: List["EmailModel"]
- size: int
diff --git a/cloudflare_temp_email/smtp_proxy_server/parse_email.py b/cloudflare_temp_email/smtp_proxy_server/parse_email.py
deleted file mode 100644
index 3c1c115..0000000
--- a/cloudflare_temp_email/smtp_proxy_server/parse_email.py
+++ /dev/null
@@ -1,69 +0,0 @@
-import datetime
-import json
-import logging
-import email
-
-from email.message import Message
-from email.mime.multipart import MIMEMultipart
-from email.mime.text import MIMEText
-
-
-from models import EmailModel
-
-_logger = logging.getLogger(__name__)
-_logger.setLevel(logging.INFO)
-
-
-def get_email_model(msg: Message):
- subparts = [
- get_email_model(subpart)
- for subpart in msg.get_payload()
- ] if msg.is_multipart() else []
- body = "" if msg.is_multipart() else msg._payload
- return EmailModel(
- headers={k: v for k, v in msg.items()},
- body=body,
- content_type=msg.get_content_type(),
- size=len(body) + sum(subpart.size for subpart in subparts),
- subparts=subparts,
- )
-
-
-def parse_email(raw: str) -> EmailModel:
- try:
- msg = email.message_from_string(raw)
- return get_email_model(msg)
- except Exception as e:
- _logger.error(f"Could not parse email: {e}")
- return EmailModel(
- headers={},
- body="could not parse email",
- content_type="text/plain",
- size=len("could not parse email"),
- subparts=[],
- )
-
-
-def generate_email_model(item: dict) -> EmailModel:
- email_json = json.loads(item["raw"])
- message = MIMEMultipart()
- if email_json.get("version") == "v2":
- message['From'] = f'{email_json["from_name"]} <{item["address"]}>' if email_json.get("from_name") else item["address"]
- message['To'] = f'{email_json["to_name"]} <{email_json["to_mail"]}>' if email_json.get("to_name") else email_json["to_mail"]
- message.attach(MIMEText(
- email_json["content"],
- "html" if email_json.get("is_html") else "plain"
- ))
- else:
- message['From'] = f'{email_json["from"]["name"]} <{email_json["from"]["email"]}>'
- message['To'] = ", ".join(
- [f"{to['name']} <{to['email']}>" for to in email_json["personalizations"][0]["to"]])
- message.attach(MIMEText(
- email_json["content"][0]["value"],
- "html" if "html" in email_json["content"][0]["type"] else "plain"
- ))
- message['Subject'] = email_json["subject"]
- message["Date"] = datetime.datetime.strptime(
- item["created_at"], "%Y-%m-%d %H:%M:%S"
- ).strftime("%a, %d %b %Y %H:%M:%S +0000")
- return parse_email(message.as_string())
diff --git a/cloudflare_temp_email/smtp_proxy_server/requirements.txt b/cloudflare_temp_email/smtp_proxy_server/requirements.txt
deleted file mode 100644
index 5d902b6..0000000
--- a/cloudflare_temp_email/smtp_proxy_server/requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-aiosmtpd==1.4.6
-pydantic-settings==2.9.1
-requests==2.32.4
-Twisted==25.5.0
-httpx==0.28.1
diff --git a/cloudflare_temp_email/smtp_proxy_server/smtp_server.py b/cloudflare_temp_email/smtp_proxy_server/smtp_server.py
deleted file mode 100644
index 7095587..0000000
--- a/cloudflare_temp_email/smtp_proxy_server/smtp_server.py
+++ /dev/null
@@ -1,154 +0,0 @@
-import asyncio
-import logging
-import email
-import httpx
-
-from aiosmtpd.controller import Controller
-from aiosmtpd.smtp import SMTP, Session, Envelope, AuthResult, LoginPassword
-
-from config import settings
-
-_logger = logging.getLogger(__name__)
-_logger.setLevel(logging.INFO)
-
-
-class CustomSMTPHandler:
-
- def authenticator(self, server, session, envelope, mechanism, auth_data):
- fail_nothandled = AuthResult(success=False, handled=False)
- if mechanism not in ("LOGIN", "PLAIN"):
- _logger.warning(f"Unsupported mechanism {mechanism}")
- return fail_nothandled
- if not isinstance(auth_data, LoginPassword):
- _logger.warning(f"Invalid auth data {auth_data}")
- return fail_nothandled
- return AuthResult(success=True, auth_data=auth_data)
-
- async def handle_DATA(self, server: SMTP, session: Session, envelope: Envelope) -> str:
- _logger.info(
- f"handle_DATA from {envelope.mail_from} to {envelope.rcpt_tos}"
- )
- if not isinstance(session.auth_data, LoginPassword):
- return '530 Authentication required'
- if len(envelope.rcpt_tos) != 1:
- return '500 Only one recipient allowed'
- # Only one recipient allowed
- to_mail = envelope.rcpt_tos[0]
- # Parse email
- msg = email.message_from_string(envelope.content)
- content_list = []
- if msg.is_multipart():
- for part in msg.walk():
- content_type = part.get_content_type()
- charset = part.get_content_charset()
- cte = str(part.get('content-transfer-encoding', '')).lower()
- if content_type not in ["text/plain", "text/html"]:
- _logger.warning(f"Skipping {content_type}")
- continue
- if cte == "8bit":
- value = part.get_payload(decode=False)
- else:
- payload = part.get_payload(decode=True)
- value = payload.decode(charset) if charset else payload
- if not value:
- continue
- content_list.append({
- "type": content_type,
- "value": value
- })
- elif msg.get_content_type() in ["text/plain", "text/html"] and msg.get_payload(decode=True):
- cte = str(msg.get('content-transfer-encoding', '')).lower()
- charset = msg.get_content_charset()
- if cte == "8bit":
- value = msg.get_payload(decode=False)
- else:
- payload = msg.get_payload(decode=True)
- value = payload.decode(charset) if charset else payload
- _logger.info(f"Payload {msg._payload} charset {charset}")
- content_list.append({
- "type": msg.get_content_type(),
- "value": value
- })
-
- if not content_list:
- return '500 Invalid content'
- body = max(
- content_list,
- key=lambda x: (x["type"] == "text/html", len(x["value"]))
- )
- from_name, _ = email.utils.parseaddr(
- str(email.header.make_header(
- email.header.decode_header(msg['From'])
- ))
- )
- to_mail_map = {}
- for to in str(email.header.make_header(
- email.header.decode_header(msg['To'])
- )).split(","):
- tmp_to_name, tmp_to_mail = email.utils.parseaddr(to)
- to_mail_map[tmp_to_mail] = tmp_to_name
- _logger.info(f"Parsed mail from {from_name} to {to_mail_map}")
- # Send mail
- send_body = {
- "token": session.auth_data.password.decode(),
- "from_name": from_name,
- "to_name": to_mail_map.get(to_mail),
- "to_mail": to_mail,
- "subject": str(email.header.make_header(
- email.header.decode_header(msg['Subject'])
- )),
- "is_html": body["type"] == "text/html",
- "content": body["value"],
- }
- _logger.info(f"Send mail {dict(send_body, token='***')}")
- try:
- res = httpx.post(
- f"{settings.proxy_url}/external/api/send_mail",
- json=send_body, headers={
- "Content-Type": "application/json"
- }
- )
- if res.status_code != 200:
- _logger.error(
- "Failed to send mail "
- f"code=[{res.status_code}] text=[{res.text}]"
- )
- return f'500 Internal server error code=[{res.status_code}] text=[{res.text}]'
- except Exception as e:
- _logger.error(e)
- return '500 Internal server error'
-
- return '250 OK'
-
-
-handler = CustomSMTPHandler()
-server = Controller(
- handler,
- hostname="",
- port=settings.port,
- auth_require_tls=False,
- decode_data=True,
- authenticator=handler.authenticator,
- auth_exclude_mechanism=["DONT"]
-)
-
-
-async def start():
- _logger.info(f"Starting server on port {settings.port}")
- server.start()
-
-
-def start_smtp_server():
- loop = asyncio.new_event_loop()
- asyncio.set_event_loop(loop)
- task = loop.create_task(start())
- try:
- loop.run_forever()
- except KeyboardInterrupt:
- _logger.info("Got KeyboardInterrupt, stopping")
- server.stop()
-
-
-if __name__ == "__main__":
- _logger.info(f"Starting server settings[{settings}]")
- start_smtp_server()
diff --git a/cloudflare_temp_email/vitepress-docs/.gitignore b/cloudflare_temp_email/vitepress-docs/.gitignore
deleted file mode 100644
index 5e23010..0000000
--- a/cloudflare_temp_email/vitepress-docs/.gitignore
+++ /dev/null
@@ -1,307 +0,0 @@
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
-
-# Custom
-dist/
-
-# User-specific files
-*.suo
-*.user
-*.userosscache
-*.sln.docstates
-
-# User-specific files (MonoDevelop/Xamarin Studio)
-*.userprefs
-
-# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
-x64/
-x86/
-bld/
-[Bb]in/
-[Oo]bj/
-[Ll]og/
-
-# Visual Studio 2015 cache/options directory
-.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUNIT
-*.VisualState.xml
-TestResult.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
-
-# .NET Core
-project.lock.json
-project.fragment.lock.json
-artifacts/
-#**/Properties/launchSettings.json
-
-*_i.c
-*_p.c
-*_i.h
-*.ilk
-*.meta
-*.obj
-*.pch
-*.pdb
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.tmp_proj
-*.log
-*.vspscc
-*.vssscc
-.builds
-*.pidb
-*.svclog
-*.scc
-
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-*.VC.db
-*.VC.VC.opendb
-
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# JustCode is a .NET coding add-in
-.JustCode
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# TODO: Comment the next line if you want to checkin your web deploy settings
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# Microsoft Azure Web App publish settings. Comment the next line if you want to
-# checkin your Azure Web App publish settings, but sensitive information contained
-# in these scripts will be unencrypted
-PublishScripts/
-
-# NuGet Packages
-*.nupkg
-# The packages folder can be ignored because of Package Restore
-**/packages/*
-# except build/, which is used as an MSBuild target.
-!**/packages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/packages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
-*.nuget.props
-*.nuget.targets
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Windows Store app package directories and files
-AppPackages/
-BundleArtifacts/
-Package.StoreAssociation.xml
-_pkginfo.txt
-
-# Visual Studio cache files
-# files ending in .cache can be ignored
-*.[Cc]ache
-# but keep track of directories ending in .cache
-!*.[Cc]ache/
-
-# Others
-ClientBin/
-~$*
-*~
-*.dbmdl
-*.dbproj.schemaview
-*.jfm
-*.pfx
-*.publishsettings
-orleans.codegen.cs
-
-# Since there are multiple workflows, uncomment next line to ignore bower_components
-# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
-#bower_components/
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-
-# SQL Server files
-*.mdf
-*.ldf
-*.ndf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-node_modules/
-
-# Typescript v1 declaration files
-typings/
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-paket-files/
-
-# FAKE - F# Make
-.fake/
-
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# CodeRush
-.cr/
-
-# Python Tools for Visual Studio (PTVS)
-__pycache__/
-*.pyc
-
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
-
-# Telerik's JustMock configuration file
-*.jmconfig
-
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
-
-/coverage
-/src/client/shared.ts
-/src/node/shared.ts
-*.log
-.DS_Store
-.vite_opt_cache
-dist
-node_modules
-TODOs.md
-.vscode
-docs/.vitepress/cache/
-docs/.vitepress/dist/
-.idea/
-
-*.zip
diff --git a/cloudflare_temp_email/vitepress-docs/docs/.vitepress/config.ts b/cloudflare_temp_email/vitepress-docs/docs/.vitepress/config.ts
deleted file mode 100644
index 6ca9145..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/.vitepress/config.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { defineConfig } from 'vitepress'
-import { zh } from './zh'
-import { en } from './en'
-
-export default defineConfig({
- title: "Temp Mail Doc",
- lang: 'zh-CN',
- lastUpdated: true,
- locales: {
- root: { label: '简体中文', ...zh },
- en: { label: 'English', ...en }
- },
- head: [
- ['link', { rel: 'icon', type: 'image/png', href: '/logo.png' }],
- ['meta', { name: 'theme-color', content: '#5f67ee' }],
- ['meta', { property: 'og:type', content: 'website' }],
- ['meta', { property: 'og:locale', content: 'Temp Mail Doc' }],
- ['meta', { property: 'og:title', content: 'Temp Mail Doc' }],
- ['meta', { property: 'og:site_name', content: 'Temp Mail' }],
- ['meta', { property: 'og:image', content: 'https://temp-mail-docs.awsl.uk/logo.png' }],
- ['meta', { property: 'og:url', content: 'https://temp-mail-docs.awsl.uk' }],
- ],
- sitemap: {
- hostname: 'https://temp-mail-docs.awsl.uk',
- transformItems(items) {
- return items.filter((item) => !item.url.includes('migration'))
- }
- },
- themeConfig: {
- logo: { src: '/logo.png', width: 24, height: 24 },
- search: { provider: 'local' },
- socialLinks: [
- {
- icon: 'discord',
- link: 'https://discord.gg/dQEwTWhA6Q'
- },
- {
- icon: {
- svg: ' '
- },
- link: 'https://t.me/cloudflare_temp_email'
- },
- {
- icon: 'github',
- link: 'https://github.com/dreamhunter2333/cloudflare_temp_email'
- }
- ]
- }
-})
diff --git a/cloudflare_temp_email/vitepress-docs/docs/.vitepress/en.ts b/cloudflare_temp_email/vitepress-docs/docs/.vitepress/en.ts
deleted file mode 100644
index c40caa0..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/.vitepress/en.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { defineConfig, type DefaultTheme } from 'vitepress'
-
-export const en = defineConfig({
- title: "Temp Mail Doc",
- lang: 'zh-Hans',
- description: 'CloudFlare Free sending and receiving of temporary domain name mailboxes',
-
- themeConfig: {
- outline: 'deep',
- nav: nav(),
-
- editLink: {
- pattern: 'https://github.com/dreamhunter2333/cloudflare_temp_email/edit/main/vitepress-docs/docs/:path',
- text: 'Edit this page on GitHub'
- },
-
- footer: {
- message: 'Based on MIT license',
- copyright: `Copyright © 2023-${new Date().getFullYear()} Dream Hunter`
- },
- }
-})
-
-function nav(): DefaultTheme.NavItem[] {
- return [
- {
- text: 'Home',
- link: '/en/',
- },
- {
- text: 'Guide',
- link: '/en/cli',
- },
- {
- text: 'Service Status',
- link: '/status',
- },
- {
- text: 'Reference',
- link: '/reference',
- },
- {
- text: process.env.TAG_NAME || 'v0.2.2',
- items: [
- {
- text: 'CHANGELOG',
- link: 'https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/CHANGELOG.md'
- },
- {
- text: 'Contribute',
- link: 'https://github.com/dreamhunter2333/cloudflare_temp_email'
- }
- ]
- }
- ]
-}
diff --git a/cloudflare_temp_email/vitepress-docs/docs/.vitepress/zh.ts b/cloudflare_temp_email/vitepress-docs/docs/.vitepress/zh.ts
deleted file mode 100644
index 8b86ab6..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/.vitepress/zh.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-import { defineConfig, type DefaultTheme } from 'vitepress'
-
-export const zh = defineConfig({
- title: "临时邮箱文档",
- lang: 'zh-Hans',
- description: 'CloudFlare 免费收发 临时域名邮箱',
-
- themeConfig: {
- nav: nav(),
-
- sidebar: {
- '/zh/guide/': { base: '/zh/guide/', items: sidebarGuide() },
- },
-
- editLink: {
- pattern: 'https://github.com/dreamhunter2333/cloudflare_temp_email/edit/main/vitepress-docs/docs/:path',
- text: '在 GitHub 上编辑此页面'
- },
-
- footer: {
- message: '基于 MIT 许可发布',
- copyright: `版权所有 © 2023-${new Date().getFullYear()} Dream Hunter`
- },
-
- docFooter: {
- prev: '上一页',
- next: '下一页'
- },
-
- outline: {
- level: 'deep',
- label: '页面导航'
- },
-
- lastUpdated: {
- text: '最后更新于',
- formatOptions: {
- dateStyle: 'short',
- timeStyle: 'medium'
- }
- },
-
- langMenuLabel: '多语言',
- returnToTopLabel: '回到顶部',
- sidebarMenuLabel: '菜单',
- darkModeSwitchLabel: '主题',
- lightModeSwitchTitle: '切换到浅色模式',
- darkModeSwitchTitle: '切换到深色模式'
- }
-})
-
-function nav(): DefaultTheme.NavItem[] {
- return [
- {
- text: '主页',
- link: '/',
- },
- {
- text: '指南',
- link: '/zh/guide/quick-start',
- activeMatch: '/zh/guide/'
- },
- {
- text: '服务状态',
- link: '/status',
- },
- {
- text: '参考',
- link: '/reference',
- },
- {
- text: process.env.TAG_NAME || 'v0.2.2',
- items: [
- {
- text: '更新日志',
- link: 'https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/CHANGELOG.md'
- },
- {
- text: '参与贡献',
- link: 'https://github.com/dreamhunter2333/cloudflare_temp_email'
- }
- ]
- }
- ]
-}
-
-function sidebarGuide(): DefaultTheme.SidebarItem[] {
- return [
- {
- text: '简介',
- collapsed: false,
- items: [
- { text: '什么是临时邮箱', link: 'what-is-temp-mail' },
- { text: 'Star History', link: 'star-history' },
- { text: '快速开始部署', link: 'quick-start' },
- ]
- },
- {
- text: '通过命令行部署',
- collapsed: false,
- items: [
- { text: '命令行部署准备', link: 'cli/pre-requisite' },
- { text: 'D1 数据库', link: 'cli/d1' },
- { text: 'Cloudflare workers 后端', link: 'cli/worker' },
- { text: '配置邮件转发', link: 'email-routing.md' },
- { text: 'Cloudflare Pages 前端', link: 'cli/pages' },
- { text: '配置发送邮件', link: 'config-send-mail' },
- ]
- },
- {
- text: '通过用户界面部署',
- collapsed: false,
- items: [
- { text: 'D1 数据库', link: 'ui/d1' },
- { text: 'Cloudflare workers 后端', link: 'ui/worker' },
- { text: '配置邮件转发', link: 'email-routing.md' },
- { text: 'Cloudflare Pages 前端', link: 'ui/pages' },
- { text: '配置发送邮件', link: 'config-send-mail' },
- ]
- },
- {
- text: '通过 Github Actions 部署',
- collapsed: true,
- items: [
- { text: 'Github Actions 部署准备', link: 'actions/pre-requisite' },
- { text: 'D1 数据库', link: 'actions/d1' },
- { text: 'Github Actions 配置', link: 'actions/github-action' },
- { text: '配置邮件转发', link: 'email-routing.md' },
- { text: '配置发送邮件', link: 'config-send-mail' },
- { text: '自动更新配置', link: 'actions/auto-update' },
- ]
- },
- {
- text: '通用',
- collapsed: false,
- items: [
- { text: 'worker变量说明', link: 'worker-vars' },
- { text: '常见问题', link: 'common-issues' },
- ]
- },
- {
- text: '附加功能',
- collapsed: false,
- items: [
- { text: '配置 SMTP IMAP 代理服务', link: 'feature/config-smtp-proxy' },
- { text: '发送邮件 API', link: 'feature/send-mail-api' },
- { text: '查看邮件 API', link: 'feature/mail-api' },
- { text: '配置子域名邮箱', link: 'feature/subdomain' },
- { text: '配置 Telegram Bot', link: 'feature/telegram' },
- { text: '配置 S3 附件', link: 'feature/s3-attachment' },
- { text: '配置 worker 使用 wasm 解析邮件', link: 'feature/mail_parser_wasm_worker' },
- { text: '配置 webhook', link: 'feature/webhook' },
- { text: '新建邮箱地址 API', link: 'feature/new-address-api' },
- { text: 'Oauth2 第三方登录', link: 'feature/user-oauth2' },
- { text: '配置其他worker增强', link: 'feature/another-worker-enhanced' },
- { text: '给网页增加 Google Ads', link: 'feature/google-ads.md' },
- ]
- },
- {
- text: '功能简介',
- collapsed: false,
- items: [
- { text: 'Admin 控制台', link: 'feature/admin' },
- { text: 'Admin 用户管理', link: 'feature/admin-user-management' },
- ]
- },
- { text: '参考', base: "/", link: 'reference' }
- ]
-}
diff --git a/cloudflare_temp_email/vitepress-docs/docs/en/cli.md b/cloudflare_temp_email/vitepress-docs/docs/en/cli.md
deleted file mode 100644
index 3f83cbb..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/en/cli.md
+++ /dev/null
@@ -1,195 +0,0 @@
-# cloudflare temp email
-
-This is a temporary email service that uses Cloudflare Workers to create a temporary email address and view the received email in web browser.
-
-## Features
-
-- [x] Cloudflare D1 as a database
-- [x] Deploy the front end with Cloudflare Pages
-- [x] Deploy the backend with Cloudflare Workers
-- [x] Email forwarding using Cloudflare Email Routing
-- [x] Use password to login to the previous mailbox again.
-- [x] Get Custom Name Email
-- [x] Support multiple languages
-- [x] Add access authorization, which can be used as a private site
-- [x] Add auto reply feature
-- [x] Add attachment viewing function
-- [x] use rust wasm to parse email
-- [x] support send email
-- [x] support DKIM
-
-## Deploy
-
-[Install/Update Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/)
-
-```bash
-npm install wrangler -g
-git clone https://github.com/dreamhunter2333/cloudflare_temp_email.git
-# Switch to the latest tag or the branch you want to deploy. You can also use the main branch directly.
-# git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
-```
-
-## DB - Cloudflare D1
-
-```bash
-# create a database, and copy the output to wrangler.toml in the next step
-wrangler d1 create dev
-wrangler d1 execute dev --file=db/schema.sql --remote
-# schema update, if you have initialized the database before this date, you can execute this command to update
-# wrangler d1 execute dev --file=db/2024-01-13-patch.sql --remote
-# wrangler d1 execute dev --file=db/2024-04-03-patch.sql --remote
-# create a namespace, and copy the output to wrangler.toml in the next step
-wrangler kv:namespace create DEV
-```
-
-
-
-### Backend - Cloudflare workers
-
-The first deployment will prompt you to create a project. Please fill in `production` for the `production` branch.
-
-```bash
-cd worker
-pnpm install
-cp wrangler.toml.template wrangler.toml
-# deploy
-pnpm run deploy
-```
-
-`wrangler.toml`
-
-```toml
-name = "cloudflare_temp_email"
-main = "src/worker.ts"
-compatibility_date = "2024-09-23"
-compatibility_flags = [ "nodejs_compat" ]
-
-# enable cron if you want set auto clean up
-# [triggers]
-# crons = [ "0 0 * * *" ]
-
-# send mail by cf mail
-# send_email = [
-# { name = "SEND_MAIL" },
-# ]
-
-[vars]
-# DEFAULT_LANG = "zh"
-# TITLE = "Custom Title" # The title of the site
-PREFIX = "tmp" # The mailbox name prefix to be processed
-# (min, max) length of the adderss, if not set, the default is (1, 30)
-# MIN_ADDRESS_LEN = 1
-# MAX_ADDRESS_LEN = 30
-# ANNOUNCEMENT = "Custom Announcement"
-# always show ANNOUNCEMENT even no changes
-# ALWAYS_SHOW_ANNOUNCEMENT = true
-# address check REGEX, if not set, will not check
-# ADDRESS_CHECK_REGEX = "^(?!.*admin).*"
-# address name replace REGEX, if not set, the default is [^a-z0-9]
-# ADDRESS_REGEX = "[^a-z0-9]"
-# If you want your site to be private, uncomment below and change your password
-# PASSWORDS = ["123", "456"]
-# admin console password, if not configured, access to the console is not allowed
-# ADMIN_PASSWORDS = ["123", "456"]
-# warning: no password or user check for admin portal
-# DISABLE_ADMIN_PASSWORD_CHECK = false
-# admin contact information. If not configured, it will not be displayed. Any string can be configured.
-# ADMIN_CONTACT = "xx@xx.xxx"
-DEFAULT_DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # domain name for no role users
-DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # all your domain name
-# For chinese domain name, you can use DOMAIN_LABELS to show chinese domain name
-# DOMAIN_LABELS = ["中文.xxx", "xxx.xxx2"]
-# USER_DEFAULT_ROLE = "vip" # default role for new users(only when enable mail verification)
-# ADMIN_USER_ROLE = "admin" # the role which can access admin panel
-# User roles configuration, if domains is empty will use default_domains, if prefix is null will use default prefix, if prefix is empty string will not use prefix
-# USER_ROLES = [
-# { domains = ["xxx.xxx1" , "xxx.xxx2"], role = "vip", prefix = "vip" },
-# { domains = ["xxx.xxx1" , "xxx.xxx2"], role = "admin", prefix = "" },
-# ]
-JWT_SECRET = "xxx" # Key used to generate jwt
-BLACK_LIST = "" # Blacklist, used to filter senders, comma separated
-# Allow users to create email addresses
-ENABLE_USER_CREATE_EMAIL = true
-# Disable anonymous user create email, if set true, users can only create email addresses after logging in
-# DISABLE_ANONYMOUS_USER_CREATE_EMAIL = true
-# Allow users to delete messages
-ENABLE_USER_DELETE_EMAIL = true
-# Allow automatic replies to emails
-ENABLE_AUTO_REPLY = false
-# Allow webhook
-# ENABLE_WEBHOOK = true
-# Footer text
-# COPYRIGHT = "Dream Hunter"
-# DISABLE_SHOW_GITHUB = true # Disable Show GitHub link
-# default send balance, if not set, it will be 0
-# DEFAULT_SEND_BALANCE = 1
-# the role which can send emails without limit, multiple roles can be separated by ,
-# NO_LIMIT_SEND_ROLE = "vip"
-# Turnstile verification configuration
-# CF_TURNSTILE_SITE_KEY = ""
-# CF_TURNSTILE_SECRET_KEY = ""
-# telegram bot
-# TG_MAX_ADDRESS = 5
-# telegram bot info, predefined bot info can reduce latency of the webhook
-# TG_BOT_INFO = "{}"
-# global forward address list, if set, all emails will be forwarded to these addresses
-# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
-# Frontend URL
-# FRONTEND_URL = "https://xxxx.xxx"
-# Enable check junk mail
-# ENABLE_CHECK_JUNK_MAIL = false
-# junk mail check list, if status exists and status is not pass, will be marked as junk mail
-# JUNK_MAIL_CHECK_LIST = = ["spf", "dkim", "dmarc"]
-# junk mail force check pass list, if no status or status is not pass, will be marked as junk mail
-# JUNK_MAIL_FORCE_PASS_LIST = ["spf", "dkim", "dmarc"]
-# remove attachment if size exceed 2MB, mail maybe mising some information due to parsing
-# REMOVE_EXCEED_SIZE_ATTACHMENT = true
-# remove all attachment, mail maybe mising some information due to parsing
-# REMOVE_ALL_ATTACHMENT = true
-
-[[d1_databases]]
-binding = "DB"
-database_name = "xxx" # D1 database name
-database_id = "xxx" # D1 database ID
-
-# kv config for send email verification code
-# [[kv_namespaces]]
-# binding = "KV"
-# id = "xxxx"
-
-# Create a new address current limiting configuration
-# [[unsafe.bindings]]
-# name = "RATE_LIMITER"
-# type = "ratelimit"
-# namespace_id = "1001"
-# # 10 requests per minute
-# simple = { limit = 10, period = 60 }
-```
-
-you can find and test the worker's url in the workers dashboard
-
-
-
-## Cloudflare Email Routing
-
-Before you can bind an email address to your Worker, you need to enable Email Routing and have at least one verified email address.
-
-enable email route and config email forward catch-all to the worker
-
-
-
-### Frontend - Cloudflare pages
-
-The first deployment will prompt you to create a project. Please fill in `production` for the `production` branch.
-
-```bash
-cd frontend
-pnpm install
-# add .env.local and modify VITE_API_BASE to your worker's url
-# VITE_API_BASE=https://xxx.xxx.workers.dev - don't put / in the end
-cp .env.example .env.local
-pnpm build --emptyOutDir
-pnpm run deploy
-```
-
-
diff --git a/cloudflare_temp_email/vitepress-docs/docs/en/index.md b/cloudflare_temp_email/vitepress-docs/docs/en/index.md
deleted file mode 100644
index 2af8c25..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/en/index.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-# https://vitepress.dev/reference/default-theme-home-page
-layout: home
-
-hero:
- name: "Temporary mailbox document"
- tagline: "Build CloudFlare to send and receive free temporary domain name mailboxes"
- actions:
- - theme: brand
- text: Try it now
- link: https://mail.awsl.uk/en
- - theme: alt
- text: command line deployment
- link: /en/cli
-features:
- - title: Free hosting on CloudFlare, no server required
- details: Cloudflare D1 database, Cloudflare Pages frontend, Cloudflare Workers backend, Cloudflare Email Routing
- - title: Only domain name required for private deployment
- details: Support password login email, access authorization can be used as a private site, support attachment function
- - title: Use rust wasm to parse emails
- details: Use rust wasm to parse emails, support various RFC standards for emails, support attachments, extremely fast
- - title: Support sending emails
- details: Support sending txt or html emails through domain name mailboxes,Support DKIM signature
----
diff --git a/cloudflare_temp_email/vitepress-docs/docs/index.md b/cloudflare_temp_email/vitepress-docs/docs/index.md
deleted file mode 100644
index e40d43d..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/index.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-# https://vitepress.dev/reference/default-theme-home-page
-layout: home
-
-hero:
- name: "临时邮箱文档"
- tagline: "搭建 CloudFlare 免费收发 临时域名邮箱"
- actions:
- - theme: brand
- text: 立即试用
- link: https://mail.awsl.uk/
- - theme: alt
- text: 命令行部署
- link: /zh/guide/quick-start
- - theme: alt
- text: 通过用户界面部署
- link: /zh/guide/quick-start
- - theme: alt
- text: 通过 Github Actions 部署
- link: /zh/guide/quick-start
-
-features:
- - title: 仅需域名即可私有部署, 免费托管在 CloudFlare,无需服务器
- details: 支持 password 登录邮箱, 用户注册,使用访问密码可作为私人站点,支持附件功能。
- - title: 使用 rust wasm 解析邮件
- details: 使用 rust wasm 解析邮件,支持邮件各种RFC标准,支持附件, 速度极快
- - title: 支持 Telegram Bot 和 Webhook
- details: 邮件可转发到 Telegram 或者 webhook, Telegram Bot 支持绑定邮箱,查看邮件, Telegram 小程序
- - title: 支持发送邮件(UI/API/SMTP)
- details: 支持通过域名邮箱发送 txt 或者 html 邮件,支持 DKIM 签名, UI/API/SMTP 发送邮件
----
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/address-webhook.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/address-webhook.png
deleted file mode 100644
index 0f6fa4a..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/address-webhook.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-mail-webhook.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-mail-webhook.png
deleted file mode 100644
index 87baed8..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-mail-webhook.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-user-management.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-user-management.png
deleted file mode 100644
index 2748a3d..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-user-management.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-user-page.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-user-page.png
deleted file mode 100644
index c9c022e..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-user-page.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-webhook-settings.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-webhook-settings.png
deleted file mode 100644
index d1dd7ed..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin-webhook-settings.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin.png
deleted file mode 100644
index 42da75c..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/admin.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-01.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-01.png
deleted file mode 100644
index baddc6e..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-01.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-02.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-02.png
deleted file mode 100644
index 534f711..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-02.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-03.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-03.png
deleted file mode 100644
index 1161435..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-03.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-04.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-04.png
deleted file mode 100644
index e2e6a81..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/another-worker-enhanced-04.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/imap.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/imap.png
deleted file mode 100644
index 98ccdac..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/imap.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/oauth2-login.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/oauth2-login.png
deleted file mode 100644
index e24eb88..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/oauth2-login.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/oauth2.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/oauth2.png
deleted file mode 100644
index 675e5f8..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/oauth2.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/s3-download.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/s3-download.png
deleted file mode 100644
index 3d4ae70..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/s3-download.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/s3-save.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/s3-save.png
deleted file mode 100644
index 309530f..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/s3-save.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/feature/telegram.png b/cloudflare_temp_email/vitepress-docs/docs/public/feature/telegram.png
deleted file mode 100644
index 34a16ca..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/feature/telegram.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/logo.png b/cloudflare_temp_email/vitepress-docs/docs/public/logo.png
deleted file mode 100644
index 97829b1..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/logo.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/d1.png b/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/d1.png
deleted file mode 100644
index 95619ba..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/d1.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/demo.png b/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/demo.png
deleted file mode 100644
index 269bf73..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/demo.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/email.png b/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/email.png
deleted file mode 100644
index 1e3cbbc..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/email.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/pages.png b/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/pages.png
deleted file mode 100644
index 8439d83..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/pages.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/worker.png b/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/worker.png
deleted file mode 100644
index 868390a..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/readme_assets/worker.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/d1-exec.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/d1-exec.png
deleted file mode 100644
index fe13113..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/d1-exec.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/d1.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/d1.png
deleted file mode 100644
index 8ee4a24..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/d1.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/pages-1.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/pages-1.png
deleted file mode 100644
index 2b3aaa5..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/pages-1.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/pages-domain.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/pages-domain.png
deleted file mode 100644
index 1e5f147..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/pages-domain.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/pages.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/pages.png
deleted file mode 100644
index 76b51d6..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/pages.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-1.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-1.png
deleted file mode 100644
index 7a21ba8..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-1.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-2.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-2.png
deleted file mode 100644
index e4685aa..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-2.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-3.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-3.png
deleted file mode 100644
index af2cb05..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-3.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-bindings.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-bindings.png
deleted file mode 100644
index f311eee..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-bindings.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-d1-1.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-d1-1.png
deleted file mode 100644
index a1b74f5..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-d1-1.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-d1-2.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-d1-2.png
deleted file mode 100644
index 279d854..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-d1-2.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-d1.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-d1.png
deleted file mode 100644
index a0b6cac..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-d1.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv-0.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv-0.png
deleted file mode 100644
index 8ad3d59..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv-0.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv-1.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv-1.png
deleted file mode 100644
index 8c6380c..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv-1.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv-2.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv-2.png
deleted file mode 100644
index 5d601e2..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv-2.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv.png
deleted file mode 100644
index 84103aa..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-kv.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-runtime.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-runtime.png
deleted file mode 100644
index afed4da..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-runtime.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-upload.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-upload.png
deleted file mode 100644
index aa7ded9..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-upload.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-var.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-var.png
deleted file mode 100644
index 9658aee..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker-var.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker_home.png b/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker_home.png
deleted file mode 100644
index b435200..0000000
Binary files a/cloudflare_temp_email/vitepress-docs/docs/public/ui_install/worker_home.png and /dev/null differ
diff --git a/cloudflare_temp_email/vitepress-docs/docs/reference.md b/cloudflare_temp_email/vitepress-docs/docs/reference.md
deleted file mode 100644
index 4b779c7..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/reference.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# Reference
-
-- https://developers.cloudflare.com/d1/
-- https://developers.cloudflare.com/pages/
-- https://developers.cloudflare.com/workers/
-- https://developers.cloudflare.com/email-routing/
diff --git a/cloudflare_temp_email/vitepress-docs/docs/status.md b/cloudflare_temp_email/vitepress-docs/docs/status.md
deleted file mode 100644
index 4b6063e..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/status.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Status Page
-
-[Status Link](https://uptime.aks.awsl.icu/status/temp-email)
-
-| Service | Status |
-| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| [Backend](https://temp-email-api.awsl.uk/) |       |
-| [Frontend](https://mail.awsl.uk/) |       |
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/auto-update.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/auto-update.md
deleted file mode 100644
index e110a8f..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/auto-update.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Github Actions 部署如何配置自动更新
-
-::: warning 注意
-有问题请通过 `Github Issues` 反馈,感谢。
-自动更新不会执行 D1 数据库的 sql 文件,当数据库 schema 变动时,需要手动执行。
-:::
-
-1. 打开仓库的 `Actions` 页面,找到 `Upstream Sync`,点击 `enable workflow` 启用 `workflow`
-2. 如果 `Upstream Sync` 运行失败,到仓库主页点击 `Sync` 手动同步即可
-3. 修改 `Upstream Sync` 的 `schedule` 配置可自定义更新间隔,参考 [cron 表达式](https://crontab.guru/)
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/d1.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/d1.md
deleted file mode 100644
index d94be15..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/d1.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# 初始化/更新 D1 数据库
-
-## 创建数据库
-
-打开 cloudflare 控制台,选择 `Workers & Pages` -> `D1` -> `Create Database`,点击创建数据库
-
-
-
-创建完成后,我们在 cloudflare 的控制台可以看到 D1 数据库,并获取到数据库的 `名称` 和 `数据库 ID`
-
-## 初始化数据库
-
-在部署完成后,在 admin 页面的 `快速设置` -> `数据库` 中,点击 `初始化数据库` 按钮来初始化数据库
-
-## 更新数据库 schema
-
-参考 [命令行更新 d1](/zh/guide/cli/d1) 或者 [用户界面更新 d1](/zh/guide/ui/d1)
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/github-action.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/github-action.md
deleted file mode 100644
index 3820271..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/github-action.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# 通过 Github Actions 部署
-
-::: warning 注意
-目前只支持 worker 和 pages 的部署。
-有问题请通过 `Github Issues` 反馈,感谢。
-
-`worker.dev` 域名在中国无法访问,请自定义域名
-:::
-
-## 部署步骤
-
-### Fork 仓库并启用 Actions
-
-- 在 GitHub fork 本仓库
-- 打开仓库的 `Actions` 页面
-- 找到 `Deploy Backend` 点击 `enable workflow` 启用 `workflow`
-- 如果需要前后端分离部署, 找到`Deploy Frontend` 点击 `enable workflow` 启用 `workflow`
-
-### 配置 Secrets
-
-然后在仓库页面 `Settings` -> `Secrets and variables` -> `Actions` -> `Repository secrets`, 添加以下 `secrets`:
-
-- 公共 `secrets`
-
- | 名称 | 说明 |
- | ----------------------- | --------------------------------------------------------------------------------------------------------------- |
- | `CLOUDFLARE_ACCOUNT_ID` | Cloudflare 账户 ID, [参考文档](https://developers.cloudflare.com/workers/wrangler/ci-cd/#cloudflare-account-id) |
- | `CLOUDFLARE_API_TOKEN` | Cloudflare API Token, [参考文档](https://developers.cloudflare.com/workers/wrangler/ci-cd/#api-token) |
-
-- worker 后端 `secrets`
-
- | 名称 | 说明 |
- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
- | `BACKEND_TOML` | 后端配置文件,[参考此处](/zh/guide/cli/worker.html#修改-wrangler-toml-配置文件) |
- | `DEBUG_MODE` | (可选) 是否开启调试模式,配置为 `true` 开启, 默认 worker 部署日志不会输出到 Github Actions 页面,开启后会输出 |
- | `BACKEND_USE_MAIL_WASM_PARSER` | (可选) 是否使用 wasm 解析邮件,配置为 `true` 开启, 功能参考 [配置 worker 使用 wasm 解析邮件](/zh/guide/feature/mail_parser_wasm_worker) |
- | `USE_WORKER_ASSETS` | (可选) 部署带有前端资源的 Worker, 配置为 `true` 开启 |
-
-- pages 前端 `secrets`
-
- > [!warning] 注意
- > 如果选择部署带有前端资源的 Worker, 则无须配置这些 `secrets`
-
- | 名称 | 说明 |
- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
- | `FRONTEND_ENV` | 前端配置文件,请复制 `frontend/.env.example` 的内容,[并参考此处修改](/zh/guide/cli/pages.html) |
- | `FRONTEND_NAME` | 你在 Cloudflare Pages 创建的项目名称,可通过 [用户界面](https://temp-mail-docs.awsl.uk/zh/guide/ui/pages.html) 或者 [命令行](https://temp-mail-docs.awsl.uk/zh/guide/cli/pages.html) 创建 |
- | `FRONTEND_BRANCH` | (可选) pages 部署的分支,可不配置,默认 `production` |
- | `TG_FRONTEND_NAME` | (可选) 你在 Cloudflare Pages 创建的项目名称,同 `FRONTEND_NAME`,如果需要 Telegram Mini App 功能,请填写 |
-
-### 部署
-
-- 打开仓库的 `Actions` 页面
-- 找到 `Deploy Backend` 点击 `Run workflow` 选择分支手动部署
-- 如果需要前后端分离部署, 找到 `Deploy Frontend`, 点击 `Run workflow` 选择分支手动部署
-
-## 如何配置自动更新
-
-1. 打开仓库的 `Actions` 页面,找到 `Upstream Sync`,点击 `enable workflow` 启用 `workflow`
-2. 如果 `Upstream Sync` 运行失败,到仓库主页点击 `Sync` 手动同步即可
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/pre-requisite.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/pre-requisite.md
deleted file mode 100644
index b7530d1..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/actions/pre-requisite.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Gihub Actions 部署准备
-
-## GitHub 账户
-
-- 需要一个 GitHub 账户
-- 良好的网络环境
-
-## Fork 仓库
-
-- 在 GitHub fork [本仓库](https://github.com/dreamhunter2333/cloudflare_temp_email.git)
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/d1.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/d1.md
deleted file mode 100644
index faad396..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/d1.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# 初始化/更新 D1 数据库
-
-第一次执行登录 wrangler 命令时,会提示登录, 按提示操作即可
-
-## 初始化数据库
-
-```bash
-cd worker
-cp wrangler.toml.template wrangler.toml
-# 创建 D1 并执行 schema.sql
-wrangler d1 create dev
-wrangler d1 execute dev --file=../db/schema.sql --remote
-```
-
-创建完成后,我们在 cloudflare 的控制台可以看到 D1 数据库
-
-
-
-## 更新数据库 schema
-
-`schema` 更新,请确认你之前部署的版本,
-查看 [更新日志](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/CHANGELOG.md)
-
-找到需要执行的 `patch` 文件, 执行, 例如:
-
-```bash
-cd worker
-wrangler d1 execute dev --file=../db/2024-01-13-patch.sql --remote
-wrangler d1 execute dev --file=../db/2024-04-03-patch.sql --remote
-```
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/pages.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/pages.md
deleted file mode 100644
index 897144e..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/pages.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# Cloudflare Pages 前端
-
-> [!warning] 注意
-> 下面几种方式选择一种即可
-
-## 部署带有前端资源的 Worker
-
-参考 [部署 Worker](/zh/guide/cli/worker#部署带有前端页面的-worker-可选)
-
-## 前后端分离部署
-
-第一次部署会提示创建项目, `production` 分支请填写 `production`
-
-```bash
-cd frontend
-pnpm install
-cp .env.example .env.prod
-```
-
-修改 `.env.prod` 文件
-
-将 `VITE_API_BASE` 修改为上一步创建的 `worker` 的 `url`, 不要在末尾加 `/`
-
-例如: `VITE_API_BASE=https://xxx.xxx.workers.dev`
-
-```bash
-pnpm build --emptyOutDir
-# 第一次部署会提示创建项目, production 分支请填写 production
-pnpm run deploy
-```
-
-部署完成之后你可以在 Cloudflare 控制台看到你的项目, 可以为 `pages` 配置自定义域名
-
-
-
-## 通过 page functions 转发后端请求
-
-从 page functions 转发请求到 worker 后端, 可以获取更快的响应速度
-
-第一次部署会提示创建项目, `production` 分支请填写 `production`
-
-如果你的 worker 后端 名称不为 `cloudflare_temp_email` 请修改 `pages/wrangler.toml`
-
-```bash
-cd frontend
-pnpm install
-# 如果你要启用 Cloudflare Zero Trust, 需要使用 pnpm build:pages:nopwa 来禁用缓存
-pnpm build:pages
-cd ../pages
-pnpm run deploy
-```
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/pre-requisite.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/pre-requisite.md
deleted file mode 100644
index aa42707..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/pre-requisite.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# 先决条件
-
-## wrangler 的安装
-
-安装 wrangler
-
-```bash
-npm install wrangler -g
-```
-
-## 克隆项目
-
-```bash
-git clone https://github.com/dreamhunter2333/cloudflare_temp_email.git
-# 切换到最新 tag 或者你想部署的分支,你也可以直接使用 main 分支
-# git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
-```
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/worker.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/worker.md
deleted file mode 100644
index d035c35..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/cli/worker.md
+++ /dev/null
@@ -1,147 +0,0 @@
-# Cloudflare Worker 后端
-
-> [!warning] 注意
-> `worker.dev` 域名在中国无法访问,请自定义域名
-
-## 初始化项目
-
-```bash
-cd worker
-pnpm install
-cp wrangler.toml.template wrangler.toml
-```
-
-## 创建 KV 缓存
-
-> [!NOTE]
-> 如果你要启用注册用户功能,并需要发送邮件验证,则需要创建 `KV` 缓存, 不需要可跳过此步骤
-> 如果需要 Telegram Bot,需要创建 `KV` 缓存,不需要可跳过此步骤
-
-通过命令行创建 KV 缓存,或者在 Cloudflare 控制台创建,然后复制对应配置到 `wrangler.toml` 文件中
-
-```bash
-wrangler kv:namespace create DEV
-```
-
-## 修改 `wrangler.toml` 配置文件
-
-> [!NOTE] 注意
-> 更多变量的配置请查看 [worker变量说明](/zh/guide/worker-vars)
-
-```toml
-name = "cloudflare_temp_email"
-main = "src/worker.ts"
-compatibility_date = "2024-09-23"
-compatibility_flags = [ "nodejs_compat" ]
-
-# 如果你想使用自定义域名,你需要添加 routes 配置
-# routes = [
-# { pattern = "temp-email-api.xxxxx.xyz", custom_domain = true },
-# ]
-
-# 如果你想要部署带有前端资源的 worker, 你需要添加 assets 配置
-# [assets]
-# directory = "../frontend/dist/"
-# binding = "ASSETS"
-# run_worker_first = true
-
-# 如果你想要使用定时任务清理邮件,取消下面的注释,并修改 cron 表达式
-# [triggers]
-# crons = [ "0 0 * * *" ]
-
-# 通过 Cloudflare 发送邮件
-# send_email = [
-# { name = "SEND_MAIL" },
-# ]
-
-[vars]
-# 邮箱名称前缀,不需要后缀可配置为空字符串或者不配置
-PREFIX = "tmp"
-# 用于临时邮箱的所有域名, 支持多个域名
-DOMAINS = ["xxx.xxx1" , "xxx.xxx2"]
-# 用于生成 jwt 的密钥, jwt 用于给用户登录以及鉴权
-JWT_SECRET = "xxx"
-
-# admin 控制台密码, 不配置则不允许访问控制台
-# ADMIN_PASSWORDS = ["123", "456"]
-
-# 是否允许用户创建邮件, 不配置则不允许
-ENABLE_USER_CREATE_EMAIL = true
-# 允许用户删除邮件, 不配置则不允许
-ENABLE_USER_DELETE_EMAIL = true
-
-# D1 数据库的名称和 ID 可以在 cloudflare 控制台查看
-[[d1_databases]]
-binding = "DB"
-database_name = "xxx" # D1 数据库名称
-database_id = "xxx" # D1 数据库 ID
-
-# kv config 用于用户注册发送邮件验证码,如果不启用用户注册或不启用注册验证,可以不配置
-# [[kv_namespaces]]
-# binding = "KV"
-# id = "xxxx"
-
-# 新建地址限流配置 /api/new_address
-# [[unsafe.bindings]]
-# name = "RATE_LIMITER"
-# type = "ratelimit"
-# namespace_id = "1001"
-# # 10 requests per minute
-# simple = { limit = 10, period = 60 }
-
-# 绑定其他 worker 处理邮件,例如通过 auth-inbox ai 能力解析验证码或激活链接
-# [[services]]
-# binding = "AUTH_INBOX"
-# service = "auth-inbox"
-```
-
-## 部署带有前端页面的 worker(可选)
-
-> [!NOTE]
-> 如果不需要 [带有前端页面的 worker],可以跳过此步骤
-> 参考之后部署前端文档,可以进行前后端分离部署
-
-确认已构建前端资源到 `frontend/dist` 目录
-
-```bash
-cd frontend
-pnpm install --no-frozen-lockfile
-pnpm build:pages
-```
-
-`worker` 目录下的 `wrangler.toml` 文件中添加下面的配置
-
-```toml
-[assets]
-directory = "../frontend/dist/"
-binding = "ASSETS"
-run_worker_first = true
-```
-
-## Telegram Bot 配置
-
-> [!NOTE]
-> 如果不需要 Telegram Bot, 可跳过此步骤
-
-请先创建一个 Telegram Bot,然后获取 `token`,然后执行下面的命令,将 `token` 添加到 secrets 中
-
-```bash
-pnpm wrangler secret put TELEGRAM_BOT_TOKEN
-```
-
-## 部署
-
-第一次部署会提示创建项目, `production` 分支请填写 `production`
-
-```bash
-pnpm run deploy
-```
-
-部署成功之后再路由中可以看到 `worker` 的 `url`,控制台也会输出 `worker` 的 `url`
-
-
-
-> [!NOTE]
-> 打开 `worker` 的 `url`,如果显示 `OK` 说明部署成功
->
-> 打开 `/health_check`,如果显示 `OK` 说明部署成功
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/common-issues.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/common-issues.md
deleted file mode 100644
index 91f2ea5..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/common-issues.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# 常见问题
-
-> [!NOTE] 注意
-> 如果你的问题没有在这里找到解决方案,请到 `Github Issues` 中搜索或者提问, 或者到 Telegram 群组中提问。
-
-## 通用
-
-| 问题 | 解决方案 |
-| -------------------------------------------------- | ------------------------------------------------------------------------------- |
-| 使用 Cloudflare Workers 给已认证的转发邮箱发送邮件 | 使用 cf 的 API 进行发送,只支持绑定到 CF 上的收件地址,即 CF EMAIL 转发目的地址 |
-| 绑定多个域名 | 每个域名都需要设置 email 转发到 worker |
-
-## worker 相关
-
-| 问题 | 解决方案 |
-| ------------------------------------------------------------------ | --------------------------------------------------------------------------- |
-| `Uncaught Error: No such module "path". imported from "worker.js"` | [参考](/zh/guide/ui/worker) |
-| `No such module "node:stream". imported from "worker.js"` | [参考](/zh/guide/ui/worker) |
-| `二级域名无法发送邮件` | [参考](https://github.com/dreamhunter2333/cloudflare_temp_email/issues/515) |
-| `Failed to send verify code: No balance` | admin 后台设置无限制邮件或者发件权限页面增加额度 |
-| `Github OAuth无法获取到邮箱 400 Failed to get user email` | 需要 github 用户设置公开邮箱 |
-| `Cannot read properties of undefined (reading 'map')` | worker 变量没有设置成功 |
-
-## pages 相关
-
-| 问题 | 解决方案 |
-| --------------- | ---------------------------------------- |
-| `network error` | 使用无痕模式或者清空浏览器缓存,DNS 缓存 |
-
-## telegram bot
-
-| 问题 | 解决方案 |
-| -------------------------------------------------------------- | -------------------------------------------------- |
-| `Telgram Bot获取邮件失败:400:Bad Request:BUTTON_URL_INVALID` | tg mini app 的 URL 填写错误,需要填写 pages 的 URL |
-| `Telegram bot bind error: bind adress count reach the limit` | 需要设置 worker 变量 `TG_MAX_ADDRESS` |
-
-## Github Actions
-
-| 问题 | 解决方案 |
-| ------------------------------------------ | --------------------------------------------------------------------------------- |
-| Github Action部署后,cf里始终是preview分支 | 到 cf pages 页面的设置中确认 前端的分支 和 Github Action 的 前端部署分支 是否相同 |
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/config-send-mail.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/config-send-mail.md
deleted file mode 100644
index 01b7da5..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/config-send-mail.md
+++ /dev/null
@@ -1,84 +0,0 @@
-
-# 配置发送邮件
-
-::: warning 注意
-三种方式可以同时配置,发送邮件时会优先使用 `resend`,如果没有配置 `resend`,则会使用 `smtp`.
-
-如果配置了 Cloudflare 已认证的转发邮箱地址,会优先使用 cf 内部 API 发送邮件
-:::
-
-## 使用 resend 发送邮件
-
-注册 `https://resend.com/domains` 根据提示添加 DNS 记录,
-
-`API KEYS` 页面创建 `api key`
-
-然后执行下面的命令,将 `RESEND_TOKEN` 添加到 secrets 中
-
-> [!NOTE]
-> 如果你觉得麻烦,也可以直接明文放在 `wrangler.toml` 中 `[vars]` 下面,但是不推荐这样做
-
-如果你是通过 UI 部署的,可以在 Cloudflare 的 UI 界面中添加到 `Variables and Secrets` 下面
-
-```bash
-# 切换到 worker 目录
-cd worker
-wrangler secret put RESEND_TOKEN
-```
-
-如果你有多个域名,对应不同的 `api key`,可以在 `wrangler.toml` 中添加多个 secret, 名称为 `RESEND_TOKEN_` + `<. 换成 _ 的 大写域名>`,例如
-
-```bash
-wrangler secret put RESEND_TOKEN_XXX_COM
-wrangler secret put RESEND_TOKEN_DREAMHUNTER2333_XYZ
-```
-
-## 使用 SMTP 发送邮件
-
-`SMTP_CONFIG` 的格式如下,key 为域名,value 为 SMTP 配置,SMTP 配置格式详情可以参考 [zou-yu/worker-mailer](https://github.com/zou-yu/worker-mailer/blob/main/README_zh-CN.md)
-
-```json
-{
- "awsl.uk": {
- "host": "smtp.xxx.com",
- "port": 465,
- "secure": true,
- "authType": [
- "plain",
- "login"
- ],
- "credentials": {
- "username": "username",
- "password": "password"
- }
- }
-}
-```
-
-然后执行下面的命令,将 `SMTP_CONFIG` 添加到 secrets 中
-
-> [!NOTE]
-> 如果你觉得麻烦,也可以直接明文放在 `wrangler.toml` 中 `[vars]` 下面,但是不推荐这样做
-
-如果你是通过 UI 部署的,可以在 Cloudflare 的 UI 界面中添加到 `Variables and Secrets` 下面
-
-```bash
-# 切换到 worker 目录
-cd worker
-wrangler secret put SMTP_CONFIG
-```
-
-## 给 Cloudflare 上已认证的转发邮箱发送邮件
-
-仅支持 CLI 部署时使用,在 `wrangler.toml` 中添加 `send_email` 配置
-
-发送的目的邮箱地址必须是 Cloudflare 上已认证的邮箱地址,局限性较大,如果需要发送邮件给其他邮箱,可以使用 `resend` 或者 `smtp` 发送邮件
-
-```toml
-# 通过 Cloudflare 发送邮件
-send_email = [
- { name = "SEND_MAIL" },
-]
-```
-
-admin 后台 账号配置 `已验证地址列表(可通过 cf 内部 api 发送邮件)`
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/email-routing.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/email-routing.md
deleted file mode 100644
index c639f8f..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/email-routing.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Cloudflare Email Routing
-
-1. 在 CF 控制台网页的对应域名的 `Email Routing` 下,配置 `电子邮件 DNS 记录`, 如果是多个域名,需要配置多个域名的 `电子邮件 DNS 记录`
-
-2. 在将电子邮件地址绑定到您的 Worker 之前,您需要启用电子邮件路由并拥有至少一个经过验证的电子邮件地址(目标地址)。
-
-3. 配置每个域名的 `Email Routing` 的路由规则中的 `Catch-all 地址` 发送到 `worker`
-
-
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/admin-user-management.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/admin-user-management.md
deleted file mode 100644
index e85c568..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/admin-user-management.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Admin 用户相关
-
-## 用户管理页面
-
-
-
-## 用户设置
-
-此处开启用户登录,以及验证等配置
-
-
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/admin.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/admin.md
deleted file mode 100644
index 8275415..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/admin.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Admin 控制台
-
-> [!NOTE]
-> 需要配置 `ADMIN_PASSWORDS` 或者 `ADMIN_USER_ROLE` 才可以访问 admin 控制台
-> admin 角色配置, 如果用户角色等于 ADMIN_USER_ROLE 则可以访问 admin 控制台
-
-部署前端应用之后,点击 左上角 logo 5 次 或者访问 `/admin` 路径即可进入管理控制台。
-
-需要在后端配置 `ADMIN_PASSWORDS` 或者当前用户角色为 `ADMIN_USER_ROLE`, 则不允许访问控制台。
-
-
-
-## 如果你的网站只可私人访问,可通过此禁用检查
-
-`DISABLE_ADMIN_PASSWORD_CHECK = true`
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/another-worker-enhanced.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/another-worker-enhanced.md
deleted file mode 100644
index 9b34bfa..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/another-worker-enhanced.md
+++ /dev/null
@@ -1,144 +0,0 @@
-# 通过其他 worker 增强
-
-> 临时邮箱的核心能力在邮件的管理,通过其他 worker 可以增强临时邮箱的功能,例如通过 auth-inbox ai 能力解析验证码或激活链接
-> 该功能仅触发其他 worker ,在 webhook 后执行
-> [!NOTE]
-> 如果要使用 worker 增强,请提前创建可以 rpc 调用的 worker,具体下文详述
-> 参考:
-> - https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/
-> - https://developers.cloudflare.com/workers/runtime-apis/rpc/
-> - auth-inbox 项目:https://github.com/TooonyChen/AuthInbox
-
-## 创建其他 worker(以 auth-inbox 项目ai解析验证码为例子)
-
-### worker 改造为继承 WorkerEntrypoint
-
-一个简单,作为被调用方,提供 rpc 方法调用的worker代码如下(rpcEmail 方法为样例)
-(使用已经修改好的项目 https://github.com/oneisall8955/AuthInbox-fork)
-
-src/index.ts 文件
-```js
-import { WorkerEntrypoint } from "cloudflare:workers";
-
-interface Env {
- DB: D1Database;
- // ...
-}
-
-export default class extends WorkerEntrypoint {
- async fetch(request: Request): Promise {
- console.log("原本fetch接口入参是request,env,ctx");
- console.log("修改为WorkerEntrypoint风格后,只有一个入参request,获取环境变量和上下文有小改动");
- // 环境变量及上下文改动详见:
- // https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/#bindings-env
- // https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/#lifecycle-methods-ctx
- const env: Env = this.env;
- const ctx: ExecutionContext = this.ctx;
- console.log("后续逻辑不变");
- return new Response('ok', { status: 200 });
- }
-
- // 主要功能
- async email(message: ForwardableEmailMessage): Promise {
- console.log("原本fetch接口入参是message,env,ctx");
- console.log("修改为WorkerEntrypoint风格后,只有一个入参message,获取环境变量和上下文和fetch方法一样");
- const env: Env = this.env;
- const ctx: ExecutionContext = this.ctx;
- console.log("接受email routing请求后,后续逻辑不变");
- }
-
- // 暴露rpc接口,处理来自其他worker的邮件请求
- async rpcEmail(requestBody: string): Promise {
- console.log(`接受其他worker(临时邮件服务cloudflare_temp_email)的请求,request body: ${requestBody}`);
- // requestBody json 格式,由临时邮件服务发送,格式如下
- // type RPCEmailMessage = {
- // from: string | undefined | null,
- // to: string | undefined | null,
- // rawEmail: string | undefined | null,
- // headers: Map,
- // }
- // ... todo ...
- }
-}
-```
-
-### 部署其他 worker
-
-修改好或者使用 以auth-inbox 为例,部署到 cloudflare worker 上,详见 https://github.com/TooonyChen/AuthInbox ,或者使用已经修改好的项目 https://github.com/oneisall8955/AuthInbox-fork
-
-## 配置临时邮件服务,使用指定其他 worker 增强
-
-## 绑定服务
-
-### 通过 wrangler.toml 配置
-
-```toml
-[[services]]
-binding = "AUTH_INBOX"
-service = "auth-inbox"
-```
-
-这里的 `binding = "AUTH_INBOX"` 可以自定义,可以是任何字符串,`service = "auth-inbox"` 是部署好的提供rpc接口调用的worker名称。
-
-### 用户界面配置
-
-在设置-绑定,添加绑定,选择绑定服务。
-变量名称填写自定义的名称,可以任意字符串 ,例如 `AUTH_INBOX`。
-服务绑定选择上一步创建好的服务,例如 `auth-inbox`。
-
-
-
-
-
-## 环境变量配置
-
-### 通过 wrangler.toml 配置
-
-```toml
-ENABLE_ANOTHER_WORKER = true
-ANOTHER_WORKER_LIST ="""
-[
- {
- "binding":"AUTH_INBOX",
- "method":"rpcEmail",
- "keywords":[
- "验证码","激活码","激活链接","确认链接","验证邮箱","确认邮件","账号激活","邮件验证","账户确认","安全码","认证码","安全验证","登陆码","确认码","启用账户","激活账户","账号验证","注册确认",
- "account","activation","verify","verification","activate","confirmation","email","code","validate","registration","login","code","expire","confirm"
- ]
- }
-]
-"""
-```
-
-环境变量解释:
-- ENABLE_ANOTHER_WORKER = true:默认为false,true则开启其他 worker 处理邮件
-- ANOTHER_WORKER_LIST 是一个JOSN数组,每个对象包3个字段
- - binding: *必填,必须与services部分指定的 binding = "XXX" 保持一致*,例子中为 AUTH_INBOX
- - method: 可选,默认 rpcEmail,指的是调用这个 worker 的哪一个 rpc 方法处理
- - keywords: 关键词数组,忽略大小写。用于过滤,如果*解析后邮件文本*匹配到这些关键词,触发这个 worker,并且调用这个 worker 的 `method` 方法
-
-### 用户界面配置
-
-在设置-环境变量,添加环境变量
-- ENABLE_ANOTHER_WORKER = true
-- ANOTHER_WORKER_LIST 为上面提及的JSON数组字符串,不再复述,详细介绍看上文
-```json
-[
- {
- "binding":"AUTH_INBOX",
- "method":"rpcEmail",
- "keywords":[
- "验证码","激活码","激活链接","确认链接","验证邮箱","确认邮件","账号激活","邮件验证","账户确认","安全码","认证码","安全验证","登陆码","确认码","启用账户","激活账户","账号验证","注册确认",
- "account","activation","verify","verification","activate","confirmation","email","code","validate","registration","login","code","expire","confirm"
- ]
- }
-]
-```
-
-
-
-## 测试
-
-发送一个邮件到临时邮箱,观察worker日志到,或者到 auth-inbox 提供的面板上查看验证码
-
-
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/config-smtp-proxy.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/config-smtp-proxy.md
deleted file mode 100644
index 68f585d..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/config-smtp-proxy.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# 搭建 SMTP IMAP 代理服务
-
-::: warning 注意
-如果你使用了 `resend`, 可直接使用 `resend` 的 `SMTP` 服务,不需要使用此服务
-:::
-
-## 为什么需要 SMTP IMAP 代理服务
-
-`SMTP` `IMAP` 的应用场景更加广泛
-
-## 如何搭建 SMTP IMAP 代理服务
-
-### Local Run
-
-```bash
-cd smtp_proxy_server/
-# 复制配置文件, 并修改配置文件
-# 你的 worker 地址,proxy_url=https://temp-email-api.xxx.xxx
-# 你的 SMTP 服务端口,port=8025
-cp .env.example .env
-python3 -m venv venv
-./venv/bin/python3 -m pip install -r requirements.txt
-./venv/bin/python3 main.py
-```
-
-### Docker Run
-
-```bash
-cd smtp_proxy_server/
-docker-compose up -d
-```
-
-修改 docker-compose.yaml 中的环境变量, 注意选择合适的 `tag`
-
-`proxy_url` 为 `worker` 的 URL 地址
-
-```yaml
-services:
- smtp_proxy_server:
- image: ghcr.io/dreamhunter2333/cloudflare_temp_email/smtp_proxy_server:latest
- # build:
- # context: .
- # dockerfile: dockerfile
- container_name: "smtp_proxy_server"
- ports:
- - "8025:8025"
- - "11143:11143"
- environment:
- - proxy_url=https://temp-email-api.xxx.xxx
- - port=8025
- - imap_port=11143
-```
-
-## 使用 Thunderbird 登录
-
-下载 [Thunderbird](https://www.thunderbird.net/en-US/)
-
-密码填写 `邮箱地址凭证`
-
-
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/google-ads.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/google-ads.md
deleted file mode 100644
index 8dd5db0..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/google-ads.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# 给网页增加 Google Ads
-
-## 命令行部署
-
-修改 `.env.prod` 文件
-
-增加下列两个变量, 具体的值请参考 [Google AdSense](https://www.google.com/adsense/start/) 的说明
-
-```txt
-VITE_GOOGLE_AD_CLIENT=ca-pub-123456
-VITE_GOOGLE_AD_SLOT=123456
-```
-
-然后执行下列命令, 重新部署 pages 即可.
-
-```bash
-pnpm build --emptyOutDir
-# 第一次部署会提示创建项目, production 分支请填写 production
-pnpm run deploy
-```
-
-## GitHub Action 部署
-
-修改 `FRONTEND_ENV`, 增加下列两个变量, 具体的值请参考 [Google AdSense](https://www.google.com/adsense/start/) 的说明, 重新部署 pages 即可.
-
-```txt
-VITE_GOOGLE_AD_CLIENT=ca-pub-123456
-VITE_GOOGLE_AD_SLOT=123456
-```
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/mail-api.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/mail-api.md
deleted file mode 100644
index d776b22..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/mail-api.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# 查看邮件 API
-
-## 通过 邮件 API 查看邮件
-
-这是一个 `python` 的例子,使用 `requests` 库查看邮件。
-
-```python
-limit = 10
-offset = 0
-res = requests.get(
- f"https://<你的worker地址>/api/mails?limit={limit}&offset={offset}",
- headers={
- "Authorization": f"Bearer {你的JWT密码}",
- # "x-custom-auth": "<你的网站密码>", # 如果启用了自定义密码
- "Content-Type": "application/json"
- }
-)
-```
-
-## admin 邮件 API
-
-支持 `address` filter 和 `keyword` filter
-
-```python
-import requests
-
-url = "https://<你的worker地址>/admin/mails"
-
-querystring = {
- "limit":"20",
- "offset":"0",
- # adress 和 keyword 为可选参数
- "address":"xxxx@awsl.uk",
- "keyword":"xxxx"
-}
-
-headers = {"x-admin-auth": "<你的Admin密码>"}
-
-response = requests.get(url, headers=headers, params=querystring)
-
-print(response.json())
-```
-
-## user 邮件 API
-
-支持 `address` filter 和 `keyword` filter
-
-```python
-import requests
-
-url = "https://<你的worker地址>/user_api/mails"
-
-querystring = {
- "limit":"20",
- "offset":"0",
- # adress 和 keyword 为可选参数
- "address":"xxxx@awsl.uk",
- "keyword":"xxxx"
-}
-
-headers = {"x-admin-auth": "<你的Admin密码>"}
-
-response = requests.get(url, headers=headers, params=querystring)
-
-print(response.json())
-```
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/mail_parser_wasm_worker.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/mail_parser_wasm_worker.md
deleted file mode 100644
index cfd1522..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/mail_parser_wasm_worker.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# mail-parser-wasm-worker
-
-> [!NOTE]
-> 如果你使用了 webhook 转发,或者 telegram bot 接受邮件,但是邮件内容是乱码,或者无法解析,你对解析的需要更高的要求,可以使用这个功能。
-
-## UI 部署
-
-1. 下载 [worker-with-wasm-mail-parser.zip](https://github.com/dreamhunter2333/cloudflare_temp_email/releases/latest/download/worker-with-wasm-mail-parser.zip)
-
-2. 回到 `Overview`,找到刚刚创建的 worker,点击 `Edit Code`, 删除原来的文件,上传 `worker.js` 和 `wasm` 后缀的文件, 点击 `Deploy`
-
- > [!NOTE]
- > 上传需要先点击左侧菜单的 Explorer,
- > 在文件列表的窗口里点击鼠标右键,在右键菜单里找到 `Upload`,
- > 请参考下面的截图
- >
- > 参考: [issues156](https://github.com/dreamhunter2333/cloudflare_temp_email/issues/156#issuecomment-2079453822)
-
- 
- 
-
-## CLI 部署
-
-### 修改代码
-
-```bash
-cd worker
-pnpm add mail-parser-wasm-worker
-```
-
-编辑 `worker/src/common.ts`, 取消注释这段代码,使用 mail-parser-wasm-worker 来解析邮件
-
-```ts
-export const commonParseMail = async (raw_mail: string | undefined | null): Promise<{
- sender: string,
- subject: string,
- text: string,
- html: string
-} | undefined> => {
- if (!raw_mail) {
- return undefined;
- }
- // 取消注释这段代码,使用 mail-parser-wasm-worker 来解析邮件 start
- // TODO: WASM parse email
- try {
- const { parse_message_wrapper } = await import('mail-parser-wasm-worker');
-
- const parsedEmail = parse_message_wrapper(raw_mail);
- return {
- sender: parsedEmail.sender || "",
- subject: parsedEmail.subject || "",
- text: parsedEmail.text || "",
- headers: parsedEmail.headers || [],
- html: parsedEmail.body_html || "",
- };
- } catch (e) {
- console.error("Failed use mail-parser-wasm-worker to parse email", e);
- }
- // 取消注释这段代码,使用 mail-parser-wasm-worker 来解析邮件 end
- try {
- const { default: PostalMime } = await import('postal-mime');
- const parsedEmail = await PostalMime.parse(raw_mail);
- return {
- sender: parsedEmail.from ? `${parsedEmail.from.name} <${parsedEmail.from.address}>` : "",
- subject: parsedEmail.subject || "",
- text: parsedEmail.text || "",
- html: parsedEmail.html || "",
- };
- }
- catch (e) {
- console.error("Failed use PostalMime to parse email", e);
- }
- return undefined;
-}
-```
-
-### 部署
-
-```bash
-cd worker
-pnpm run deploy
-```
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/new-address-api.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/new-address-api.md
deleted file mode 100644
index f80fbd4..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/new-address-api.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# 新建邮箱地址 API
-
-## 通过 admin API 新建邮箱地址
-
-这是一个 `python` 的例子,使用 `requests` 库发送邮件。
-
-```python
-res = requests.post(
- # 替换 xxxx.xxxx 为你的 worker 域名
- "https://xxxx.xxxx/admin/new_address",
- json={
- # 是否启用前缀 (True/False)
- "enablePrefix": True,
- "name": "<邮箱名称>",
- "domain": "<邮箱域名>",
- },
- headers={
- 'x-admin-auth': "<你的网站admin密码>",
- "Content-Type": "application/json"
- }
-)
-
-# 返回值 {"jwt": ""}
-print(res.json())
-```
-
-## 批量创建随机用户名邮箱地址 API 示例
-
-### 通过 admin API 批量新建邮箱地址
-
-这是一个 `python` 的例子,使用 `requests` 库发送邮件。
-
-```python
-import requests
-import random
-import string
-from concurrent.futures import ThreadPoolExecutor, as_completed
-
-
-def generate_random_name():
- # 生成5位英文字符
- letters1 = ''.join(random.choices(string.ascii_lowercase, k=5))
- # 生成1-3个数字
- numbers = ''.join(random.choices(string.digits, k=random.randint(1, 3)))
- # 生成1-3个英文字符
- letters2 = ''.join(random.choices(string.ascii_lowercase, k=random.randint(1, 3)))
- # 组合成最终名称
- return letters1 + numbers + letters2
-
-
-def fetch_email_data(name):
- try:
- res = requests.post(
- "https://",
- json={
- "enablePrefix": True,
- "name": name,
- "domain": "<邮箱域名>",
- },
- headers={
- 'x-admin-auth': "<你的网站admin密码>",
- "Content-Type": "application/json"
- }
- )
-
- if res.status_code == 200:
- response_data = res.json()
- email = response_data.get("address", "无地址")
- jwt = response_data.get("jwt", "无jwt")
- return f"{email}----{jwt}\n"
- else:
- print(f"请求失败,状态码: {res.status_code}")
- return None
- except requests.RequestException as e:
- print(f"请求出现错误: {e}")
- return None
-
-
-def generate_and_save_emails(num_emails):
- with ThreadPoolExecutor(max_workers=30) as executor, open('email.txt', 'a') as file:
- futures = [executor.submit(fetch_email_data, generate_random_name()) for _ in range(num_emails)]
-
- for future in as_completed(futures):
- result = future.result()
- if result:
- file.write(result)
-
-
-# 生成10个邮箱并追加到现有文件
-generate_and_save_emails(10)
-
-```
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/s3-attachment.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/s3-attachment.md
deleted file mode 100644
index 31d0167..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/s3-attachment.md
+++ /dev/null
@@ -1,34 +0,0 @@
-# 配置 S3 附件
-
-## 配置
-
-> [!NOTE]
-> 如果不需要 S3 附件, 可跳过此步骤
-
-在 Cloudflare 创建一个 R2 bucket, 你也可以使用其他的 S3 服务(如有 bug 请提 issue)
-
-参考: [配置 Cloudflare R2 的 cors](https://developers.cloudflare.com/r2/buckets/cors/#add-cors-policies-from-the-dashboard)
-
-参考 [Cloudflare R2 s3 toke](https://developers.cloudflare.com/r2/api/s3/tokens/) 创建 token, 拿到 `ENDPOINT`, `Access Key ID` 和 `Secret Access Key`,然后执行下面的命令添加到 secrets 中
-
-> [!NOTE]
-> 你也可以在 Cloudflare worker 的 UI 界面中添加 `secrets`
-
-```bash
-cd worker
-pnpm wrangler secret put S3_ENDPOINT
-pnpm wrangler secret put S3_ACCESS_KEY_ID
-pnpm wrangler secret put S3_SECRET_ACCESS_KEY
-# 请注意这里的 bucket 是你的 bucket 名称
-pnpm wrangler secret put S3_BUCKET
-```
-
-## 使用
-
-保存附件
-
-
-
-下载附件
-
-
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/send-mail-api.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/send-mail-api.md
deleted file mode 100644
index befb9ce..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/send-mail-api.md
+++ /dev/null
@@ -1,67 +0,0 @@
-# 发送邮件 API
-
-## 通过 HTTP API 发送邮件
-
-这是一个 `python` 的例子,使用 `requests` 库发送邮件。
-
-```python
-send_body = {
- "from_name": "发件人名字",
- "to_name": "收件人名字",
- "to_mail": "收件人地址",
- "subject": "邮件主题",
- "is_html": False, # 根据内容设置是否为 HTML
- "content": "<邮件内容:html 或者 文本>",
-}
-
-res = requests.post(
- "http://localhost:8787/api/send_mail",
- json=send_body, headers={
- "Authorization": f"Bearer {你的JWT密码}",
- # "x-custom-auth": "<你的网站密码>", # 如果启用了自定义密码
- "Content-Type": "application/json"
- }
-)
-
-# 使用 body 验证
-send_body = {
- "token": "<你的JWT密码>",
- "from_name": "发件人名字",
- "to_name": "收件人名字",
- "to_mail": "收件人地址",
- "subject": "邮件主题",
- "is_html": False, # 根据内容设置是否为 HTML
- "content": "<邮件内容:html 或者 文本>",
-}
-res = requests.post(
- "http://localhost:8787/external/api/send_mail",
- json=send_body, headers={
- "Content-Type": "application/json"
- }
-)
-```
-
-## 通过 SMTP 发送邮件
-
-请先参考 [配置 SMTP 代理](/zh/guide/feature/config-smtp-proxy.html)。
-
-这是一个 `python` 的例子,使用 `smtplib` 库发送邮件。
-
-`JWT令牌密码`: 即为邮箱登录密码,可以在 UI 界面中查看密码菜单中查看。
-
-```python
-import smtplib
-
-from email.mime.text import MIMEText
-from email.mime.multipart import MIMEMultipart
-
-
-with smtplib.SMTP('localhost', 8025) as smtp:
- smtp.login("jwt", "此处填写你的JWT令牌密码")
- message = MIMEMultipart()
- message['From'] = "Me "
- message['To'] = "Admin "
- message['Subject'] = "测试主题"
- message.attach(MIMEText("测试内容", 'html'))
- smtp.sendmail("me@awsl.uk", "admin@awsl.uk", message.as_string())
-```
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/subdomain.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/subdomain.md
deleted file mode 100644
index 0ede8ee..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/subdomain.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# 配置子域名邮箱
-
-::: warning 注意
-子域名邮箱发送邮件可能无法发送邮件,建议使用主域名邮箱发送邮件,子域名邮箱仅用于接收邮件。
-
-mail channel 已不被支持,下面参考中仅限收件部分。
-:::
-
-参考
-
-- [配置子域名邮箱](https://github.com/dreamhunter2333/cloudflare_temp_email/issues/164#issuecomment-2082612710)
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/telegram.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/telegram.md
deleted file mode 100644
index 14d62ba..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/telegram.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# 配置 Telegram Bot
-
-试用地址:[@cf_temp_mail_bot](https://t.me/cf_temp_mail_bot)
-
-::: warning 注意
-worker 默认的 `worker.dev` 域名的证书是不被 telegram 支持的,配置 Telegram Bot 请使用自定义域名
-:::
-
-> [!NOTE]
-> 如果要使用 Telegram Bot, 请先绑定 `KV`
->
-> 如果不需要 Telegram Bot, 可跳过此步骤
->
-> 如果你想 Telegram 的解析邮件能力更强,参考 [配置 worker 使用 wasm 解析邮件](/zh/guide/feature/mail_parser_wasm_worker)
-
-## Telegram Bot 配置
-
-请先创建一个 Telegram Bot,然后获取 `token`,然后执行下面的命令,将 `token` 添加到 secrets 中
-
-> [!NOTE]
-> 如果你觉得麻烦,也可以直接明文放在 `wrangler.toml` 中 `[vars]` 下面,但是不推荐这样做
-
-如果你是通过 UI 部署的,可以在 Cloudflare 的 UI 界面中添加到 `Variables and Secrets` 下面
-
-```bash
-# 切换到 worker 目录
-cd worker
-pnpm wrangler secret put TELEGRAM_BOT_TOKEN
-```
-
-## Bot
-
-- 可设置白名单用户
-- 点击`初始化`即可完成配置。
-- 点击`查看状态`,可以查看当前配置的状态。
-
-
-
-## Mini App
-
-可以通过命令行部署,或者 UI 界面部署
-
-### UI 部署
-
-其他步骤参考 [UI 部署](/zh/guide/cli/pages) 中的 `前后端分离部署`
-
-> [!NOTE]
-> 从这里下载 zip, [telegram-frontend.zip](https://github.com/dreamhunter2333/cloudflare_temp_email/releases/latest/download/telegram-frontend.zip)
->
-> 修改压缩包里面的 index-xxx.js 文件 ,xx 是随机的字符串
->
-> 搜索 `https://temp-email-api.xxx.xxx` ,替换成你worker 的域名,然后部署新的zip文件
-
-### 命令行部署
-
-```bash
-cd frontend
-pnpm install
-cp .env.example .env.prod
-# --project-name 可以单独为 mini app 创建一个 pages, 你也可以公用一个 pages,但是可能遇到 js 加载不了的问题
-pnpm run deploy:telegram --project-name=<你的项目名称>
-```
-
-- 部署完成后,请在 admin 后台的 `设置` -> `电报小程序` 页面 `电报小程序 URL` 中填写网页 URL。
-- 请在 `@BotFather` 处执行 `/setmenubutton`,然后输入你的网页地址,设置左下角的 `Open App` 按钮。
-- 请在 `@BotFather` 处执行 `/newapp` 新建 app 来注册 mini app。
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/user-oauth2.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/user-oauth2.md
deleted file mode 100644
index a679fdc..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/user-oauth2.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# OAuth2 第三方登录
-
-> [!WARNING] 注意
-> 第三方登录会自动使用用户邮箱注册账号(邮箱相同将视为同一账号)
->
-> 此账号和注册的账号相同, 也可以通过忘记密码设置密码
-
-## 在第三方平台注册 OAuth2
-
-### GitHub
-
-- 请先创建一个 OAuth App,然后获取 `Client ID` 和 `Client Secret`
-
-参考 [Creating an OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
-
-### Authentik
-
-- [Authentik OAuth2 Provider](https://docs.goauthentik.io/docs/providers/oauth2/)
-
-## Admin 后台配置 OAuth2
-
-
-
-## 测试用户登录页面
-
-
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/webhook.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/webhook.md
deleted file mode 100644
index b5d8ae5..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/feature/webhook.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# 配置 webhook
-
-> [!NOTE]
-> 如果要使用 webhook,请先绑定 `KV` 并且 `worker` 变量配置 `ENABLE_WEBHOOK = true`
->
-> 如果你想 webhook 的解析邮件能力更强,参考 [配置 worker 使用 wasm 解析邮件](/zh/guide/feature/mail_parser_wasm_worker)
-
-## 前提条件
-
-你需要自建一个 `webhook 服务` 或者 使用 `第三方平台`,这个服务需要能够接收 `POST` 请求,并且能够解析 `json` 数据。
-
-本项目使用了 [songquanpeng/message-pusher](https://github.com/songquanpeng/message-pusher) 示例作为 webhook 服务。
-
-- 可以使用 [msgpusher.com](https://msgpusher.com) 提供的服务
-- 也可以自建 `message-pusher` 服务,参考 [songquanpeng/message-pusher](https://github.com/songquanpeng/message-pusher)
-
-## admin 配置全局 webhook
-
-
-
-## admin 允许邮箱使用 webhook
-
-
-
-## 某个邮箱配置 webhook
-
-
-
-## webhook 数据格式
-
-要获取 url 需要配置 worker 的 `FRONTEND_URL` 为你的前端地址,或者你可以通过 `id` 自己拼接 url = `${FRONTEND_URL}?mail_id=${id}`
-
-```json
-{
- "id": "${id}",
- "url": "${url}",
- "from": "${from}",
- "to": "${to}",
- "subject": "${subject}",
- "raw": "${raw}",
- "parsedText": "${parsedText}",
- "parsedHtml": "${parsedHtml}",
-}
-```
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/quick-start.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/quick-start.md
deleted file mode 100644
index f6ed57c..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/quick-start.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# 快速开始
-
-## 开始之前
-
-需要 `良好的网络环境` 和 `cloudflare 账号`, 打开 [cloudflare控制台](https://dash.cloudflare.com/)
-
-请选择下面三种方式之一进行部署
-
-- [通过命令行部署](/zh/guide/cli/pre-requisite)
-- [通过用户界面部署](/zh/guide/ui/d1)
-- [通过Github Actions 部署](/zh/guide/actions/pre-requisite)
-
-### 也可以参考网友提供的详细的小白教程
-
-- [【教程】小白也能看懂的自建Cloudflare临时邮箱教程(域名邮箱)](https://linux.do/t/topic/316819/1)
-
-## 升级流程
-
-首先确认当前的版本,然后访问 [Release 页面](https://github.com/dreamhunter2333/cloudflare_temp_email/releases/) 和 [CHANGELOG 页面](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/CHANGELOG.md) 中找到当前的版本
-
-> [!WARNING] 注意
-> 需要注意 `Breaking Changes` 是必须进行 `数据库 sql 执行` 或者 `变量配置` 的
-
-然后查看从当前版本往后的所有更改,需要注意 `Breaking Changes` 是必须进行 `数据库 sql 执行` 或者 `变量配置` 的, 其他的功能更新按需配置即可
-
-然后参考下面的文档使用 `CLI` 或者 `UI` 覆盖部署之前的 `worker` 和 `pages` 即可
-
-### CLI 部署
-
-- [命令行更新 d1](/zh/guide/cli/d1)
-- [命令行部署 worker](/zh/guide/cli/worker)
-- [命令行部署 pages](/zh/guide/cli/worker)
-
-### UI 部署
-
-- [用户界面更新 d1](/zh/guide/ui/d1)
-- [用户界面部署 worker](/zh/guide/ui/worker)
-- [用户界面部署 pages](/zh/guide/ui/pages)
-
-### Github Actions 部署
-
-- [Github Actions 部署如何配置自动更新](/zh/guide/actions/auto-update)
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/star-history.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/star-history.md
deleted file mode 100644
index c5bf7cb..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/star-history.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Star History
-
-
-
-
-
-
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/ui/d1.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/ui/d1.md
deleted file mode 100644
index 145bec4..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/ui/d1.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# 初始化/更新 D1 数据库
-
-## 创建数据库
-
-打开 cloudflare 控制台,选择 `Storage & Databases` -> `D1 SQL Database` -> `Create Database`,点击创建数据库
-
-
-
-创建完成后,我们在 cloudflare 的控制台可以看到 D1 数据库
-
-## 初始化数据库
-
-::: warning 注意
-你也可以跳过初始化数据库,在部署完成后,在 admin 页面的 `快速设置` -> `数据库` 中,点击 `初始化数据库` 按钮来初始化数据库
-:::
-
-打开 `Console` 标签页,输入仓库中 [db/schema.sql](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/db/schema.sql) 文件的内容,点击 `Execute` 执行
-
-
-
-## 更新数据库 schema
-
-`schema` 更新,请确认你之前部署的版本,
-
-查看 [更新日志](https://github.com/dreamhunter2333/cloudflare_temp_email/blob/main/CHANGELOG.md)
-
-找到需要执行的 `patch` 文件, 执行, 例如: `db/2024-01-13-patch.sql`
-
-打开 `Console` 标签页,输入 `patch` 文件的内容,点击 `Execute` 执行
-
-
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/ui/pages.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/ui/pages.md
deleted file mode 100644
index f6efa61..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/ui/pages.md
+++ /dev/null
@@ -1,105 +0,0 @@
-# Cloudflare Pages 前端
-
-
-
-1. 点击 `Compute (Workers)` -> `Workers & Pages` -> `Create`
-
- 
-
-2. 选择 `Pages`,选择 `Use direct upload`
-
- 
-
-3. 输入部署的 worker 的地址, 地址不要带 `/`,点击生成,成功会出现下载按钮,你会得到一个 zip 包
- - 此处 worker 域名为后端 api 的域名,比如我部署在 `https://temp-email-api.awsl.uk`,则填写 `https://temp-email-api.awsl.uk`
- - 如果你的域名是 `https://temp-email-api.xxx.workers.dev`,则填写 `https://temp-email-api.xxx.workers.dev`
-
- > [!warning] 注意
- > `worker.dev` 域名在中国无法访问,请自定义域名
-
-
-
- > [!NOTE]
- > 你也可以手动部署,从这里下载 zip, [frontend.zip](https://github.com/dreamhunter2333/cloudflare_temp_email/releases/latest/download/frontend.zip)
- >
- > 修改压缩包里面的 index-xxx.js 文件 ,xx 是随机的字符串
- >
- > 搜索 `https://temp-email-api.xxx.xxx` ,替换成你worker 的域名,然后部署新的zip文件
-
-4. 选择 `Pages`,点击 `Create Pages`, 修改名称,上传下载的 zip 包,然后点击 `Deploy`
-
- 
-
-5. 打开 刚刚部署的 `Pages`,点击 `Custom Domain` 这里可以添加自己的域名,你也可以使用自动生成的 `*.pages.dev` 的域名。能打开域名说明部署成功。
-
- 
-
-
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/ui/worker.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/ui/worker.md
deleted file mode 100644
index 085fe23..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/ui/worker.md
+++ /dev/null
@@ -1,107 +0,0 @@
-# Cloudflare workers 后端
-
-> [!warning] 注意
-> `worker.dev` 域名在中国无法访问,请自定义域名
-
-1. 点击 `Compute (Workers)` -> `Workers & Pages` -> `Create`
-
- 
-
-2. 选择 `Worker`,点击 `Create Worker`, 修改名称然后点击 `Deploy`
-
- 
- 
-
-3. 回到 `Workers & Pages`,找到刚刚创建的 worker,点击 `Settings` -> `Runtime`, 修改 `Compatibility flags`, 手动输入增加 `nodejs_compat`, 兼容日期也需要大于图片中的日期。
-
- 
-
-4. 下载 [worker.js](https://github.com/dreamhunter2333/cloudflare_temp_email/releases/latest/download/worker.js)
-
-5. 回到 `Overview`,找到刚刚创建的 worker,点击 `Edit Code`, 删除原来的文件,上传 `worker.js`, 点击 `Deploy`
-
- > [!NOTE]
- > 上传需要先点击左侧菜单的 Explorer,
- > 在文件列表的窗口里点击鼠标右键,在右键菜单里找到 `Upload`,
- > 请参考下面的截图
- >
- > 参考: [issues156](https://github.com/dreamhunter2333/cloudflare_temp_email/issues/156#issuecomment-2079453822)
-
- 
-
- 
-
-6. 点击 `Settings` -> `Variables and Secrets`, 如图所示添加变量
-
- 
-
- > [!NOTE] 注意
- > 更多变量的配置请查看 [worker变量说明](/zh/guide/worker-vars)
- >
- > 注意字符串格式的变量的最外层的引号是不需要的
- >
- > 对于 `USER_ROLES` 请配置为此格式 `[{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"vip","prefix":"vip"},{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"admin","prefix":""}]`
-
- 建议配置的变量列表
-
- | 变量名 | 类型 | 说明 | 示例 |
- | -------------------------- | ----------- | ------------------------------------------ | ------------------------------------ |
- | `PREFIX` | 文本 | 新建邮箱名称默认前缀,不需要前缀可不配置 | `tmp` |
- | `DOMAINS` | JSON | 用于临时邮箱的所有域名, 支持多个域名 | `["awsl.uk", "dreamhunter2333.xyz"]` |
- | `JWT_SECRET` | 文本/Secret | 用于生成 jwt 的密钥, jwt 用于登录以及鉴权 | `xxx` |
- | `ADMIN_PASSWORDS` | JSON | admin 控制台密码, 不配置则不允许访问控制台 | `["123", "456"]` |
- | `ENABLE_USER_CREATE_EMAIL` | 文本/JSON | 是否允许用户创建邮箱, 不配置则不允许 | `true` |
- | `ENABLE_USER_DELETE_EMAIL` | 文本/JSON | 是否允许用户删除邮件, 不配置则不允许 | `true` |
-
-7. 点击 `Settings` -> `Bindings`, 点击 `Add Binding`, 名称如图,选择刚刚创建的 D1 数据库,点击 `Add Binding`
-
- > [!NOTE] 重要
- > 注意此处 `D1 Database` 的绑定名称必须为 `DB`
-
- 
-
- 
-
- 
-
-8. 点击 `Settings` -> `Trggers`, 这里可以添加自己的域名,你也可以使用自动生成的 `*.workers.dev` 的域名。记录下这个域名,后面部署前端会用到。
-
- > [!NOTE]
- > 打开 `worker` 的 `url`,如果显示 `OK` 说明部署成功
- >
- > 打开 `/health_check`,如果显示 `OK` 说明部署成功
-
- 
-
-9. 如果你要启用注册用户功能,并需要发送邮件验证,则需要创建 `KV` 缓存, 不需要可跳过此步骤
-
- > [!NOTE] 重要
- > 如果你要启用注册用户功能,并需要发送邮件验证,则需要创建 `KV` 缓存, 不需要可跳过此步骤
- >
- > 注意此处 `KV` 的绑定名称必须为 `KV`
-
- 点击 `Storage & Databases` -> `KV` -> `Create Namespace`, 如图,点击 `Create Namespace`
-
- 
-
- 
-
- 然后点击 `Settings` -> `Bindings`, 点击 `Add Binding`, 名称如图,选择刚刚创建的 KV,点击 `Add Binding`
-
- 
-
- 
-
- 
-
-10. Telegram Bot 配置
-
- > [!NOTE]
- > 如果不需要 Telegram Bot, 可跳过此步骤
-
- 请先创建一个 Telegram Bot,然后获取 `token`,将 `token` 添加到 `Variables and Secrets` 中, 变量名称: `TELEGRAM_BOT_TOKEN`
-
-11. 如果你想要使用 admin 页面中的定时任务清理邮件,需要到 `Settings` -> `Trigger Events` -> `Cron Triggers` 中添加定时任务.
-
- > [!NOTE]
- > 选择 `cron` 表达式,输入 `0 0 * * *`(此表达式表示每天午夜运行),点击 `Add` 增加。请根据您的需求调整此表达式。
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/what-is-temp-mail.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/what-is-temp-mail.md
deleted file mode 100644
index 382c3d3..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/what-is-temp-mail.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# 临时邮箱简介
-
-## 什么是临时邮箱
-
-临时邮箱,也被称为一次性邮箱或临时邮件地址,是一种用于临时接收邮件的虚拟邮箱。与常规邮箱不同,临时邮箱旨在提供一种匿名且临时的邮件接收解决方案。
-
-临时邮箱往往由网站或在线服务提供商提供,用户可以在需要注册或接收验证邮件时使用临时邮箱地址,而无需暴露自己的真实邮箱地址。这样做的好处是可以保护个人隐私
diff --git a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/worker-vars.md b/cloudflare_temp_email/vitepress-docs/docs/zh/guide/worker-vars.md
deleted file mode 100644
index d5c61e1..0000000
--- a/cloudflare_temp_email/vitepress-docs/docs/zh/guide/worker-vars.md
+++ /dev/null
@@ -1,142 +0,0 @@
-# Worker 变量说明
-
-> [!NOTE] 注意
-> 通过 CLI 部署时的写法请参考 `worker/wrangler.toml.template`
-
-## 必填变量
-
-| 变量名 | 类型 | 说明 | 示例 |
-| -------------------------- | ----------- | ------------------------------------------ | ------------------------------------ |
-| `DOMAINS` | JSON | 用于临时邮箱的所有域名, 支持多个域名 | `["awsl.uk", "dreamhunter2333.xyz"]` |
-| `JWT_SECRET` | 文本/Secret | 用于生成 jwt 的密钥, jwt 用于登录以及鉴权 | `xxx` |
-| `ADMIN_PASSWORDS` | JSON | admin 控制台密码, 不配置则不允许访问控制台 | `["123", "456"]` |
-| `ENABLE_USER_CREATE_EMAIL` | 文本/JSON | 是否允许用户创建邮箱, 不配置则不允许 | `true` |
-| `ENABLE_USER_DELETE_EMAIL` | 文本/JSON | 是否允许用户删除邮件, 不配置则不允许 | `true` |
-
-## 后台相关变量
-
-| 变量名 | 类型 | 说明 | 示例 |
-| ------------------------------ | --------- | ------------------------------------ | ---------------- |
-| `PASSWORDS` | JSON | 网站私有密码, 配置后需要密码才能访问 | `["123", "456"]` |
-| `DISABLE_ADMIN_PASSWORD_CHECK` | 文本/JSON | 警告: 管理员控制台没有密码或用户检查 | `false` |
-
-## 邮箱相关变量
-
-| 变量名 | 类型 | 说明 | 示例 |
-| ------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
-| `PREFIX` | 文本 | 新建 `邮箱名称` 的默认前缀,不需要前缀可不配置 | `tmp` |
-| `MIN_ADDRESS_LEN` | 数字 | `邮箱名称` 的最小长度 | `1` |
-| `MAX_ADDRESS_LEN` | 数字 | `邮箱名称` 的最大长度 | `30` |
-| `DISABLE_CUSTOM_ADDRESS_NAME` | 文本/JSON | 禁用自定义邮箱地址名称,如果设置为 true,则用户无法输入自定义邮箱名称,将由后台自动生成 | `true` |
-| `ADDRESS_CHECK_REGEX` | 文本 | `邮箱名称` 的正则表达式, 只用于检查 | `^(?!.*admin).*` |
-| `ADDRESS_REGEX` | 文本 | `邮箱名称` 替换非法符号的正则表达式, 不在其中的符号将被替换,如果不设置,默认为 `[^a-z0-9]`, 需谨慎使用, 有些符号可能导致无法收件 | `[^a-z0-9]` |
-| `DEFAULT_DOMAINS` | JSON | 默认用户可用的域名(未登录或未分配角色的用户) | `["awsl.uk", "dreamhunter2333.xyz"]` |
-| `CREATE_ADDRESS_DEFAULT_DOMAIN_FIRST` | 文本/JSON | 创建新地址时是否优先使用默认域名,如果设置为 true,当未指定域名时将使用第一个域名, 主要用于 telegram bot 场景 | `false` |
-| `DOMAIN_LABELS` | JSON | 对于中文域名,可以使用 DOMAIN_LABELS 显示域名的中文展示名称 | `["中文.awsl.uk", "dreamhunter2333.xyz"]` |
-| `ENABLE_AUTO_REPLY` | 文本/JSON | 允许自动回复邮件 | `true` |
-| `DEFAULT_SEND_BALANCE` | 文本/JSON | 默认发送邮件余额,如果不设置,将为 0 | `1` |
-
-## 接受邮件相关变量
-
-| 变量名 | 类型 | 说明 | 示例 |
-| ------------------------------- | --------- | -------------------------------------------------------------------------- | -------------------------- |
-| `BLACK_LIST` | 文本 | 黑名单,用于过滤发件人,逗号分隔 | `gov.cn,edu.cn` |
-| `ENABLE_CHECK_JUNK_MAIL` | 文本/JSON | 是否启用垃圾邮件检查,配合下列两个列表使用 | `false` |
-| `JUNK_MAIL_CHECK_LIST` | JSON | 垃圾邮件检查配置, 任何一项 `存在` 且 `不通过` 则被判定为垃圾邮件 | `["spf", "dkim", "dmarc"]` |
-| `JUNK_MAIL_FORCE_PASS_LIST` | JSON | 垃圾邮件检查配置, 任何一项 `不存在` 或者 `不通过` 则被判定为垃圾邮件 | `["spf", "dkim", "dmarc"]` |
-| `FORWARD_ADDRESS_LIST` | JSON | 全局转发地址列表,如果不配置则不启用,启用后所有邮件都会转发到列表中的地址 | `["xxx@xxx.com"]` |
-| `REMOVE_EXCEED_SIZE_ATTACHMENT` | 文本/JSON | 如果附件大小超过 2MB,则删除附件,邮件可能由于解析而丢失一些信息 | `true` |
-| `REMOVE_ALL_ATTACHMENT` | 文本/JSON | 移除所有附件,邮件可能由于解析而丢失一些信息 | `true` |
-
-> [!NOTE]
-> `垃圾邮件检查` 和 `移除附件功能` 需要解析邮件,免费版 CPU 有限,可能会导致大邮件解析超时
->
-> 如果你想解析邮件能力更强
->
-> 参考 [配置 worker 使用 wasm 解析邮件](/zh/guide/feature/mail_parser_wasm_worker)
-
-## webhook 相关变量
-
-| 变量名 | 类型 | 说明 | 示例 |
-| ---------------- | --------- | ------------------------------------- | ------------------ |
-| `ENABLE_WEBHOOK` | 文本/JSON | 是否启用 webhook | `true` |
-| `FRONTEND_URL` | 文本 | 前端地址,用于发送 webhook 的邮件 url | `https://xxxx.xxx` |
-
-> [!NOTE]
-> webhook 功能需要解析邮件,免费版 CPU 有限,可能会导致大邮件解析超时
->
-> 如果你想解析邮件能力更强
->
-> 参考 [配置 worker 使用 wasm 解析邮件](/zh/guide/feature/mail_parser_wasm_worker)
-
-## 用户相关变量
-
-| 变量名 | 类型 | 说明 | 示例 |
-| ------------------------------------- | --------- | ------------------------------------------------------------------------ | ------- |
-| `USER_DEFAULT_ROLE` | 文本 | 新用户默认角色, 仅在启用邮件验证时有效 | `vip` |
-| `ADMIN_USER_ROLE` | 文本 | admin 角色配置, 如果用户角色等于 ADMIN_USER_ROLE 则可以访问 admin 控制台 | `admin` |
-| `USER_ROLES` | JSON | - | 见下方 |
-| `DISABLE_ANONYMOUS_USER_CREATE_EMAIL` | 文本/JSON | 禁用匿名用户创建邮箱,如果设置为 true,则用户只能在登录后创建邮箱地址 | `true` |
-| `NO_LIMIT_SEND_ROLE` | 文本 | 可以无限发送邮件的角色, 多个角色使用逗号分割 `vip,admin` | `vip` |
-
-> [!NOTE] USER_ROLES 用户角色配置说明
->
-> - 如果 `domains` 为空将使用 `DEFAULT_DOMAINS`
-> - 如果 prefix 为 null 将使用默认前缀, 如果 prefix 为空字符串将不使用前缀
->
-> 通过用户界面部署时 `USER_ROLES` 请配置为此格式 `[{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"vip","prefix":"vip"},{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"admin","prefix":""}]`
->
-> CLI 部署时 `USER_ROLES` 请参考 `worker/wrangler.toml.template` 配置为此格式 `[{ domains = ["awsl.uk", "dreamhunter2333.xyz"], role = "vip", prefix = "vip" }, { domains = ["awsl.uk", "dreamhunter2333.xyz"], role = "admin", prefix = "" }]`
-
-## 网页相关变量
-
-| 变量名 | 类型 | 说明 | 示例 |
-| -------------------------- | ----------- | ------------------------------------------------ | --------------------- |
-| `DEFAULT_LANG` | 文本 | Worker 错误信息默认语言, zh/en | `zh` |
-| `TITLE` | 文本 | 自定义前端页面网站标题,支持 html | `Custom Title` |
-| `ANNOUNCEMENT` | 文本 | 自定义前端页面公告,支持 html | `Custom Announcement` |
-| `ALWAYS_SHOW_ANNOUNCEMENT` | 文本/JSON | 是否总是显示公告(即使无更改), 默认 `false` | `true` |
-| `COPYRIGHT` | 文本 | 自定义前端界面页脚文本,支持 html | `Dream Hunter` |
-| `ADMIN_CONTACT` | 文本 | admin 联系方式,可配置任意字符串, 不配置则不显示 | `xxx@gmail.com` |
-| `DISABLE_SHOW_GITHUB` | 文本/JSON | 是否显示 GitHub 链接 | `true` |
-| `CF_TURNSTILE_SITE_KEY` | 文本/Secret | Turnstile 人机验证配置 | `xxx` |
-| `CF_TURNSTILE_SECRET_KEY` | 文本/Secret | Turnstile 人机验证配置 | `xxx` |
-
-## Telegram Bot 相关变量
-
-| 变量名 | 类型 | 说明 | 示例 |
-| ---------------- | ---- | ---------------------------------------------------------------------- | ---- |
-| `TG_MAX_ADDRESS` | 数字 | telegram bot 最多绑定邮箱数量 | `5` |
-| `TG_BOT_INFO` | 文本 | 可不配置,telegram BOT_INFO,预定义的 BOT_INFO 可以降低 webhook 的延迟 | `{}` |
-
-> [!NOTE]
-> Telegram 功能需要解析邮件,免费版 CPU 有限,可能会导致大邮件解析超时
->
-> 如果你想解析邮件能力更强
->
-> 参考 [配置 worker 使用 wasm 解析邮件](/zh/guide/feature/mail_parser_wasm_worker)
-
-## 其他变量
-
-| 变量名 | 类型 | 说明 | 示例 |
-| ----------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
-| `ENABLE_ANOTHER_WORKER` | 文本/JSON | 是否开启其他 worker 处理邮件 | `false` |
-| `ANOTHER_WORKER_LIST` | JSON | - 其他 worker 处理邮件的配置,可以配置多个其他 worker - 通过关键词筛选,调用对应绑定的 worker 的方法(默认方法名为 rpcEmail) - keywords必填,否则 worker 将不会被触发 | 见下方 |
-
-> [!NOTE]
-> `ANOTHER_WORKER_LIST` 的配置示例
->
-> ```toml
-> #ANOTHER_WORKER_LIST ="""
-> #[
-> # {
-> # "binding":"AUTH_INBOX",
-> # "method":"rpcEmail",
-> # "keywords":[
-> # "验证码","激活码","激活链接","确认链接","验证邮箱","确认邮件","账号激活","邮件验证","账户确认","安全码","认证码","安全验证","登陆码","确认码","启用账户","激活账户","账号验证","注册确认",
-> # "account","activation","verify","verification","activate","confirmation","email","code","validate","registration","login","code","expire","confirm"
-> # ]
-> # }
-> #]
-> #
-> ```
diff --git a/cloudflare_temp_email/vitepress-docs/package.json b/cloudflare_temp_email/vitepress-docs/package.json
deleted file mode 100644
index c1e8e9c..0000000
--- a/cloudflare_temp_email/vitepress-docs/package.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "name": "temp-mail-docs",
- "private": true,
- "version": "1.0.5",
- "type": "module",
- "devDependencies": {
- "@types/node": "^24.3.1",
- "vitepress": "^1.6.4",
- "wrangler": "^4.34.0"
- },
- "scripts": {
- "dev": "vitepress dev docs",
- "build": "vitepress build docs",
- "preview": "vitepress preview docs",
- "deploy": "npm run build && wrangler pages deploy ./docs/.vitepress/dist --project-name=temp-mail-docs --branch production"
- },
- "dependencies": {
- "jszip": "^3.10.1"
- },
- "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
-}
diff --git a/cloudflare_temp_email/worker/.editorconfig b/cloudflare_temp_email/worker/.editorconfig
deleted file mode 100644
index 64ab260..0000000
--- a/cloudflare_temp_email/worker/.editorconfig
+++ /dev/null
@@ -1,13 +0,0 @@
-# http://editorconfig.org
-root = true
-
-[*]
-indent_style = tab
-tab_width = 2
-end_of_line = lf
-charset = utf-8
-trim_trailing_whitespace = true
-insert_final_newline = true
-
-[*.yml]
-indent_style = space
diff --git a/cloudflare_temp_email/worker/.gitignore b/cloudflare_temp_email/worker/.gitignore
deleted file mode 100644
index ae1cca8..0000000
--- a/cloudflare_temp_email/worker/.gitignore
+++ /dev/null
@@ -1,134 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-lerna-debug.log*
-.pnpm-debug.log*
-
-# Diagnostic reports (https://nodejs.org/api/report.html)
-report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
-
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-*.lcov
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# Bower dependency directory (https://bower.io/)
-bower_components
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (https://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directories
-node_modules/
-jspm_packages/
-
-# Snowpack dependency directory (https://snowpack.dev/)
-web_modules/
-
-# TypeScript cache
-*.tsbuildinfo
-
-# Optional npm cache directory
-.npm
-
-# Optional eslint cache
-.eslintcache
-
-# Optional stylelint cache
-.stylelintcache
-
-# Microbundle cache
-.rpt2_cache/
-.rts2_cache_cjs/
-.rts2_cache_es/
-.rts2_cache_umd/
-
-# Optional REPL history
-.node_repl_history
-
-# Output of 'npm pack'
-*.tgz
-
-# Yarn Integrity file
-.yarn-integrity
-
-# dotenv environment variable files
-.env
-.env.development.local
-.env.test.local
-.env.production.local
-.env.local
-
-# parcel-bundler cache (https://parceljs.org/)
-.cache
-.parcel-cache
-
-# Next.js build output
-.next
-out
-
-# Nuxt.js build / generate output
-.nuxt
-dist
-
-# Gatsby files
-.cache/
-# Comment in the public line in if your project uses Gatsby and not Next.js
-# https://nextjs.org/blog/next-9-1#public-directory-support
-# public
-
-# vuepress build output
-.vuepress/dist
-
-# vuepress v2.x temp and cache directory
-.temp
-.cache
-
-# Docusaurus cache and generated files
-.docusaurus
-
-# Serverless directories
-.serverless/
-
-# FuseBox cache
-.fusebox/
-
-# DynamoDB Local files
-.dynamodb/
-
-# TernJS port file
-.tern-port
-
-# Stores VSCode versions used for testing VSCode extensions
-.vscode-test
-
-# yarn v2
-.yarn/cache
-.yarn/unplugged
-.yarn/build-state.yml
-.yarn/install-state.gz
-.pnp.*
-
-.wrangler
-wrangler.toml
-.dev.vars
diff --git a/cloudflare_temp_email/worker/.prettierrc b/cloudflare_temp_email/worker/.prettierrc
deleted file mode 100644
index 5c7b5d3..0000000
--- a/cloudflare_temp_email/worker/.prettierrc
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "printWidth": 140,
- "singleQuote": true,
- "semi": true,
- "useTabs": true
-}
diff --git a/cloudflare_temp_email/worker/eslint.config.js b/cloudflare_temp_email/worker/eslint.config.js
deleted file mode 100644
index 2ec21da..0000000
--- a/cloudflare_temp_email/worker/eslint.config.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import globals from "globals";
-import pluginJs from "@eslint/js";
-import tseslint from "typescript-eslint";
-
-
-export default [
- {
- languageOptions: { globals: globals.browser },
- },
- pluginJs.configs.recommended,
- ...tseslint.configs.recommended,
- {
- rules: {
- "@typescript-eslint/no-unused-vars": "off",
- "@typescript-eslint/no-explicit-any": "off",
- "@typescript-eslint/ban-ts-comment": "off",
- }
- }
-];
diff --git a/cloudflare_temp_email/worker/package.json b/cloudflare_temp_email/worker/package.json
deleted file mode 100644
index cf56535..0000000
--- a/cloudflare_temp_email/worker/package.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "name": "cloudflare_temp_email",
- "version": "1.0.5",
- "private": true,
- "type": "module",
- "scripts": {
- "dev": "wrangler dev",
- "lint": "eslint src",
- "deploy": "wrangler deploy --minify",
- "start": "wrangler dev",
- "build": "wrangler deploy --dry-run --outdir dist --minify"
- },
- "devDependencies": {
- "@cloudflare/workers-types": "^4.20250904.0",
- "@eslint/js": "9.18.0",
- "@simplewebauthn/types": "10.0.0",
- "@types/node": "^22.18.1",
- "eslint": "9.18.0",
- "globals": "^15.15.0",
- "typescript-eslint": "^8.42.0",
- "wrangler": "^4.34.0"
- },
- "dependencies": {
- "@aws-sdk/client-s3": "^3.879.0",
- "@aws-sdk/s3-request-presigner": "^3.879.0",
- "@simplewebauthn/server": "10.0.1",
- "hono": "^4.9.6",
- "jsonpath-plus": "^10.3.0",
- "mimetext": "^3.0.27",
- "postal-mime": "^2.4.4",
- "resend": "^4.8.0",
- "telegraf": "4.16.3",
- "worker-mailer": "^1.1.5"
- },
- "pnpm": {
- "patchedDependencies": {
- "telegraf@4.16.3": "patches/telegraf@4.16.3.patch"
- }
- },
- "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
-}
diff --git a/cloudflare_temp_email/worker/patches/telegraf@4.16.3.patch b/cloudflare_temp_email/worker/patches/telegraf@4.16.3.patch
deleted file mode 100644
index 59a2a10..0000000
--- a/cloudflare_temp_email/worker/patches/telegraf@4.16.3.patch
+++ /dev/null
@@ -1,173 +0,0 @@
-diff --git a/lib/core/network/client.js b/lib/core/network/client.js
-index 25fbbbb47c7f88e83ae26f629e5ae1a0c141725c..ab82c5391596ee2ed8dbe4128f3ba57a8cc998ca 100644
---- a/lib/core/network/client.js
-+++ b/lib/core/network/client.js
-@@ -1,18 +1,18 @@
- "use strict";
--var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
-+var __createBinding = (this && this.__createBinding) || (Object.create ? (function (o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- var desc = Object.getOwnPropertyDescriptor(m, k);
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
-- desc = { enumerable: true, get: function() { return m[k]; } };
-+ desc = { enumerable: true, get: function () { return m[k]; } };
- }
- Object.defineProperty(o, k2, desc);
--}) : (function(o, m, k, k2) {
-+}) : (function (o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- o[k2] = m[k];
- }));
--var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
-+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function (o, v) {
- Object.defineProperty(o, "default", { enumerable: true, value: v });
--}) : function(o, v) {
-+}) : function (o, v) {
- o["default"] = v;
- });
- var __importStar = (this && this.__importStar) || function (mod) {
-@@ -29,8 +29,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
- /* eslint @typescript-eslint/restrict-template-expressions: [ "error", { "allowNumber": true, "allowBoolean": true } ] */
- const crypto = __importStar(require("crypto"));
- const fs = __importStar(require("fs"));
--const promises_1 = require("fs/promises");
--const https = __importStar(require("https"));
-+// const promises_1 = require("fs/promises");
-+// const https = __importStar(require("https"));
- const path = __importStar(require("path"));
- const node_fetch_1 = __importDefault(require("node-fetch"));
- const check_1 = require("../helpers/check");
-@@ -39,7 +39,8 @@ const multipart_stream_1 = __importDefault(require("./multipart-stream"));
- const error_1 = __importDefault(require("./error"));
- const url_1 = require("url");
- // eslint-disable-next-line @typescript-eslint/no-var-requires
--const debug = require('debug')('telegraf:client');
-+// const debug = require('debug')('telegraf:client');
-+const debug = (msg) => console.debug(`[telegraf:client] ${msg}`);
- const { isStream } = multipart_stream_1.default;
- const WEBHOOK_REPLY_METHOD_ALLOWLIST = new Set([
- 'answerCallbackQuery',
-@@ -61,10 +62,10 @@ const DEFAULT_OPTIONS = {
- apiRoot: 'https://api.telegram.org',
- apiMode: 'bot',
- webhookReply: true,
-- agent: new https.Agent({
-- keepAlive: true,
-- keepAliveMsecs: 10000,
-- }),
-+ // agent: new https.Agent({
-+ // keepAlive: true,
-+ // keepAliveMsecs: 10000,
-+ // }),
- attachmentAgent: undefined,
- testEnv: false,
- };
-@@ -112,9 +113,9 @@ async function buildFormDataConfig(payload, agent) {
- }
- const boundary = crypto.randomBytes(32).toString('hex');
- const formData = new multipart_stream_1.default(boundary);
-- await Promise.all(Object.keys(payload).map((key) =>
-- // @ts-expect-error payload[key] can obviously index payload, but TS doesn't trust us
-- attachFormValue(formData, key, payload[key], agent)));
-+ await Promise.all(Object.keys(payload).map((key) =>
-+ // @ts-expect-error payload[key] can obviously index payload, but TS doesn't trust us
-+ attachFormValue(formData, key, payload[key], agent)));
- return {
- method: 'POST',
- compress: true,
-@@ -205,14 +206,15 @@ async function attachFormMedia(form, media, id, agent) {
- if ('source' in media && media.source) {
- let mediaSource = media.source;
- if (typeof media.source === 'string') {
-- const source = await (0, promises_1.realpath)(media.source);
-- if ((await (0, promises_1.stat)(source)).isFile()) {
-- fileName = (_c = media.filename) !== null && _c !== void 0 ? _c : path.basename(media.source);
-- mediaSource = await fs.createReadStream(media.source);
-- }
-- else {
-- throw new TypeError(`Unable to upload '${media.source}', not a file`);
-- }
-+ throw new TypeError(`Unable to upload '${media.source}', not a file`);
-+ // const source = await (0, promises_1.realpath)(media.source);
-+ // if ((await (0, promises_1.stat)(source)).isFile()) {
-+ // fileName = (_c = media.filename) !== null && _c !== void 0 ? _c : path.basename(media.source);
-+ // mediaSource = await fs.createReadStream(media.source);
-+ // }
-+ // else {
-+ // throw new TypeError(`Unable to upload '${media.source}', not a file`);
-+ // }
- }
- if (isStream(mediaSource) || Buffer.isBuffer(mediaSource)) {
- form.addPart({
-diff --git a/lib/core/network/polling.js b/lib/core/network/polling.js
-index 42f20a5090304c56d0970da56eeaaacaa518ca92..0ae889c32d46e33440c62ad6d27a290c0fe3dda2 100644
---- a/lib/core/network/polling.js
-+++ b/lib/core/network/polling.js
-@@ -6,10 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
- exports.Polling = void 0;
- const abort_controller_1 = __importDefault(require("abort-controller"));
- const debug_1 = __importDefault(require("debug"));
--const util_1 = require("util");
-+// const util_1 = require("util");
- const error_1 = require("./error");
- const debug = (0, debug_1.default)('telegraf:polling');
--const wait = (0, util_1.promisify)(setTimeout);
-+// const wait = (0, util_1.promisify)(setTimeout);
- function always(x) {
- return () => x;
- }
-@@ -47,7 +47,8 @@ class Polling {
- (err instanceof error_1.TelegramError && err.code >= 500)) {
- const retryAfter = (_b = (_a = err.parameters) === null || _a === void 0 ? void 0 : _a.retry_after) !== null && _b !== void 0 ? _b : 5;
- debug('Failed to fetch updates, retrying after %ds.', retryAfter, err);
-- await wait(retryAfter * 1000);
-+ // await wait(retryAfter * 1000);
-+ await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
- continue;
- }
- if (err instanceof error_1.TelegramError &&
-diff --git a/lib/telegraf.js b/lib/telegraf.js
-index 23d021c3d5f98493bd714a2114ec8fa853560e5c..90094d18316138b7e12eab42f722e69ccc9b6c1f 100644
---- a/lib/telegraf.js
-+++ b/lib/telegraf.js
-@@ -28,8 +28,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.Telegraf = void 0;
- const crypto = __importStar(require("crypto"));
--const http = __importStar(require("http"));
--const https = __importStar(require("https"));
-+// const http = __importStar(require("http"));
-+// const https = __importStar(require("https"));
- const composer_1 = require("./composer");
- const compact_1 = require("./core/helpers/compact");
- const context_1 = __importDefault(require("./context"));
-@@ -157,13 +157,13 @@ class Telegraf extends composer_1.Composer {
- const callback = typeof cb === 'function'
- ? (req, res) => webhookCb(req, res, () => cb(req, res))
- : webhookCb;
-- this.webhookServer =
-- tlsOptions != null
-- ? https.createServer(tlsOptions, callback)
-- : http.createServer(callback);
-- this.webhookServer.listen(port, host, () => {
-- debug('Webhook listening on port: %s', port);
-- });
-+ // this.webhookServer =
-+ // tlsOptions != null
-+ // ? https.createServer(tlsOptions, callback)
-+ // : http.createServer(callback);
-+ // this.webhookServer.listen(port, host, () => {
-+ // debug('Webhook listening on port: %s', port);
-+ // });
- return this;
- }
- secretPathComponent() {
-@@ -176,7 +176,7 @@ class Telegraf extends composer_1.Composer {
- /**
- * @see https://github.com/telegraf/telegraf/discussions/1344#discussioncomment-335700
- */
-- async launch(config = {},
-+ async launch(config = {},
- /** @experimental */
- onLaunch) {
- var _a, _b;
diff --git a/cloudflare_temp_email/worker/src/admin_api/admin_mail_api.ts b/cloudflare_temp_email/worker/src/admin_api/admin_mail_api.ts
deleted file mode 100644
index f075f9f..0000000
--- a/cloudflare_temp_email/worker/src/admin_api/admin_mail_api.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { Context } from "hono";
-import { handleListQuery } from "../common";
-
-export default {
- getMails: async (c: Context) => {
- const { address, limit, offset, keyword } = c.req.query();
- const addressQuery = address ? `address = ?` : "";
- const addressParams = address ? [address] : [];
- const keywordQuery = keyword ? `raw like ?` : "";
- const keywordParams = keyword ? [`%${keyword}%`] : [];
- const filterQuerys = [addressQuery, keywordQuery].filter((item) => item).join(" and ");
- const finalQuery = filterQuerys.length > 0 ? `where ${filterQuerys}` : "";
- const filterParams = [...addressParams, ...keywordParams]
- return await handleListQuery(c,
- `SELECT * FROM raw_mails ${finalQuery}`,
- `SELECT count(*) as count FROM raw_mails ${finalQuery}`,
- filterParams, limit, offset
- );
- },
- getUnknowMails: async (c: Context) => {
- const { limit, offset } = c.req.query();
- return await handleListQuery(c,
- `SELECT * FROM raw_mails where address NOT IN (select name from address) `,
- `SELECT count(*) as count FROM raw_mails`
- + ` where address NOT IN (select name from address) `,
- [], limit, offset
- );
- },
- deleteMail: async (c: Context) => {
- const { id } = c.req.param();
- const { success } = await c.env.DB.prepare(
- `DELETE FROM raw_mails WHERE id = ? `
- ).bind(id).run();
- return c.json({
- success: success
- })
- }
-}
diff --git a/cloudflare_temp_email/worker/src/admin_api/admin_user_api.ts b/cloudflare_temp_email/worker/src/admin_api/admin_user_api.ts
deleted file mode 100644
index b1aa24b..0000000
--- a/cloudflare_temp_email/worker/src/admin_api/admin_user_api.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-import { Context } from 'hono';
-
-import { CONSTANTS } from '../constants';
-import { getJsonSetting, saveSetting, checkUserPassword, getDomains, getUserRoles } from '../utils';
-import { UserSettings, GeoData, UserInfo } from "../models";
-import { handleListQuery } from '../common'
-import UserBindAddressModule from '../user_api/bind_address';
-import i18n from '../i18n';
-
-export default {
- getSetting: async (c: Context) => {
- const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
- const settings = new UserSettings(value);
- return c.json(settings)
- },
- saveSetting: async (c: Context) => {
- const value = await c.req.json();
- const settings = new UserSettings(value);
- if (settings.enableMailVerify && !c.env.KV) {
- return c.text("Please enable KV first if you want to enable mail verify", 403)
- }
- if (settings.enableMailVerify && !settings.verifyMailSender) {
- return c.text("Please provide verifyMailSender", 400)
- }
- if (settings.enableMailVerify && settings.verifyMailSender) {
- const mailDomain = settings.verifyMailSender.split("@")[1];
- const domains = getDomains(c);
- if (!domains.includes(mailDomain)) {
- return c.text(`VerifyMailSender(${settings.verifyMailSender}) domain must in ${JSON.stringify(domains, null, 2)}`, 400)
- }
- }
- if (settings.maxAddressCount < 0) {
- return c.text("Invalid maxAddressCount", 400)
- }
- await saveSetting(c, CONSTANTS.USER_SETTINGS_KEY, JSON.stringify(settings));
- return c.json({ success: true })
- },
- getUsers: async (c: Context) => {
- const { limit, offset, query } = c.req.query();
- if (query) {
- return await handleListQuery(c,
- `SELECT u.id as id, u.user_email, u.created_at, u.updated_at,`
- + ` ur.role_text as role_text,`
- + ` (SELECT COUNT(*) FROM users_address WHERE user_id = u.id) AS address_count`
- + ` FROM users u`
- + ` LEFT JOIN user_roles ur ON u.id = ur.user_id`
- + ` where u.user_email like ?`,
- `SELECT count(*) as count FROM users where user_email like ?`,
- [`%${query}%`], limit, offset
- );
- }
- return await handleListQuery(c,
- `SELECT u.id as id, u.user_email, u.created_at, u.updated_at,`
- + ` ur.role_text as role_text,`
- + ` (SELECT COUNT(*) FROM users_address WHERE user_id = u.id) AS address_count`
- + ` FROM users u`
- + ` LEFT JOIN user_roles ur ON u.id = ur.user_id`,
- `SELECT count(*) as count FROM users`,
- [], limit, offset
- );
- },
- createUser: async (c: Context) => {
- const { email, password } = await c.req.json();
- if (!email || !password) {
- return c.text("Invalid email or password", 400)
- }
- // geo data
- const reqIp = c.req.raw.headers.get("cf-connecting-ip")
- const geoData = new GeoData(reqIp, c.req.raw.cf as any);
- const userInfo = new UserInfo(geoData, email);
- try {
- checkUserPassword(password);
- const { success } = await c.env.DB.prepare(
- `INSERT INTO users (user_email, password, user_info)`
- + ` VALUES (?, ?, ?)`
- ).bind(
- email, password, JSON.stringify(userInfo)
- ).run();
- if (!success) {
- return c.text("Failed to register", 500)
- }
- } catch (e) {
- const errorMsg = (e as Error).message;
- if (errorMsg && errorMsg.includes("UNIQUE")) {
- return c.text("User already exists", 400)
- }
- return c.text(`Failed to register: ${errorMsg}`, 500)
- }
- return c.json({ success: true })
- },
- deleteUser: async (c: Context) => {
- const { user_id } = c.req.param();
- const msgs = i18n.getMessagesbyContext(c);
- if (!user_id) return c.text(msgs.UserNotFoundMsg, 400);
- const { success } = await c.env.DB.prepare(
- `DELETE FROM users WHERE id = ?`
- ).bind(user_id).run();
- const { success: addressSuccess } = await c.env.DB.prepare(
- `DELETE FROM users_address WHERE user_id = ?`
- ).bind(user_id).run();
- if (!success || !addressSuccess) {
- return c.text("Failed to delete user", 500)
- }
- return c.json({ success: true })
- },
- resetPassword: async (c: Context) => {
- const { user_id } = c.req.param();
- const { password } = await c.req.json();
- const msgs = i18n.getMessagesbyContext(c);
- if (!user_id) return c.text(msgs.UserNotFoundMsg, 400);
- try {
- checkUserPassword(password);
- const { success } = await c.env.DB.prepare(
- `UPDATE users SET password = ? WHERE id = ?`
- ).bind(password, user_id).run();
- if (!success) {
- return c.text("Failed to reset password", 500)
- }
- } catch (e) {
- return c.text(`Failed to reset password: ${(e as Error).message}`, 500)
- }
- return c.json({ success: true });
- },
- updateUserRoles: async (c: Context) => {
- const { user_id, role_text } = await c.req.json();
- if (!user_id) return c.text("Invalid user_id", 400);
- if (!role_text) {
- const { success } = await c.env.DB.prepare(
- `DELETE FROM user_roles WHERE user_id = ?`
- ).bind(user_id).run();
- if (!success) {
- return c.text("Failed to update user roles", 500)
- }
- return c.json({ success: true })
- }
- const user_roles = getUserRoles(c);
- if (!user_roles.find((r) => r.role === role_text)) {
- return c.text("Invalid role_text", 400)
- }
- const { success } = await c.env.DB.prepare(
- `INSERT INTO user_roles (user_id, role_text)`
- + ` VALUES (?, ?)`
- + ` ON CONFLICT(user_id) DO UPDATE SET role_text = ?, updated_at = datetime('now')`
- ).bind(user_id, role_text, role_text).run();
- if (!success) {
- return c.text("Failed to update user roles", 500)
- }
- return c.json({ success: true })
- },
- bindAddress: async (c: Context) => {
- const {
- user_email, address, user_id, address_id
- } = await c.req.json();
- const db_user_id = user_id ?? await c.env.DB.prepare(
- `SELECT id FROM users WHERE user_email = ?`
- ).bind(user_email).first("id");
- const db_address_id = address_id ?? await c.env.DB.prepare(
- `SELECT id FROM address WHERE name = ?`
- ).bind(address).first("id");
- return await UserBindAddressModule.bindByID(c, db_user_id, db_address_id);
- },
- getBindedAddresses: async (c: Context) => {
- const { user_id } = c.req.param();
- const results = await UserBindAddressModule.getBindedAddressesById(c, user_id);
- return c.json({
- results: results,
- });
- },
-}
diff --git a/cloudflare_temp_email/worker/src/admin_api/cleanup_api.ts b/cloudflare_temp_email/worker/src/admin_api/cleanup_api.ts
deleted file mode 100644
index 5f1893b..0000000
--- a/cloudflare_temp_email/worker/src/admin_api/cleanup_api.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Context } from 'hono';
-
-import { cleanup } from '../common';
-import { CONSTANTS } from '../constants';
-import { getJsonSetting, saveSetting } from '../utils';
-import { CleanupSettings } from '../models';
-
-export default {
- cleanup: async (c: Context) => {
- const { cleanType, cleanDays } = await c.req.json();
- try {
- await cleanup(c, cleanType, cleanDays);
- } catch (error) {
- console.error(error);
- return c.text(`Failed to cleanup ${(error as Error).message}`, 500)
- }
- return c.json({ success: true })
- },
- getCleanup: async (c: Context) => {
- const cleanupSetting = await getJsonSetting(c, CONSTANTS.AUTO_CLEANUP_KEY);
- return c.json(cleanupSetting)
- },
- saveCleanup: async (c: Context) => {
- const cleanupSetting = await c.req.json();
- await saveSetting(c, CONSTANTS.AUTO_CLEANUP_KEY, JSON.stringify(cleanupSetting));
- return c.json({ success: true })
- }
-}
diff --git a/cloudflare_temp_email/worker/src/admin_api/db_api.ts b/cloudflare_temp_email/worker/src/admin_api/db_api.ts
deleted file mode 100644
index 0d070ed..0000000
--- a/cloudflare_temp_email/worker/src/admin_api/db_api.ts
+++ /dev/null
@@ -1,156 +0,0 @@
-import { Context } from "hono";
-import { CONSTANTS } from "../constants";
-import utils from "../utils";
-
-const DB_INIT_QUERIES = `
-CREATE TABLE IF NOT EXISTS raw_mails (
- id INTEGER PRIMARY KEY,
- message_id TEXT,
- source TEXT,
- address TEXT,
- raw TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_raw_mails_address ON raw_mails(address);
-
-CREATE TABLE IF NOT EXISTS address (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT UNIQUE,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_address_name ON address(name);
-
-CREATE TABLE IF NOT EXISTS auto_reply_mails (
- id INTEGER PRIMARY KEY,
- source_prefix TEXT,
- name TEXT,
- address TEXT UNIQUE,
- subject TEXT,
- message TEXT,
- enabled INTEGER DEFAULT 1,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_auto_reply_mails_address ON auto_reply_mails(address);
-
-CREATE TABLE IF NOT EXISTS address_sender (
- id INTEGER PRIMARY KEY,
- address TEXT UNIQUE,
- balance INTEGER DEFAULT 0,
- enabled INTEGER DEFAULT 1,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_address_sender_address ON address_sender(address);
-
-CREATE TABLE IF NOT EXISTS sendbox (
- id INTEGER PRIMARY KEY,
- address TEXT,
- raw TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_sendbox_address ON sendbox(address);
-
-CREATE TABLE IF NOT EXISTS settings (
- key TEXT PRIMARY KEY,
- value TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE TABLE IF NOT EXISTS users (
- id INTEGER PRIMARY KEY,
- user_email TEXT UNIQUE NOT NULL,
- password TEXT NOT NULL,
- user_info TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_users_user_email ON users(user_email);
-
-CREATE TABLE IF NOT EXISTS users_address (
- id INTEGER PRIMARY KEY,
- user_id INTEGER,
- address_id INTEGER UNIQUE,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_users_address_user_id ON users_address(user_id);
-
-CREATE INDEX IF NOT EXISTS idx_users_address_address_id ON users_address(address_id);
-
-CREATE TABLE IF NOT EXISTS user_roles (
- id INTEGER PRIMARY KEY,
- user_id INTEGER UNIQUE NOT NULL,
- role_text TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id);
-
-CREATE TABLE IF NOT EXISTS user_passkeys (
- id INTEGER PRIMARY KEY,
- user_id INTEGER NOT NULL,
- passkey_name TEXT NOT NULL,
- passkey_id TEXT NOT NULL,
- passkey TEXT NOT NULL,
- counter INTEGER DEFAULT 0,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX IF NOT EXISTS idx_user_passkeys_user_id ON user_passkeys(user_id);
-
-CREATE UNIQUE INDEX IF NOT EXISTS idx_user_passkeys_user_id_passkey_id ON user_passkeys(user_id, passkey_id);
-`
-
-export default {
- initialize: async (c: Context) => {
- // remove all \r and \n characters from the query string
- // split by ; and join with a ;\n
- const query = DB_INIT_QUERIES.replace(/[\r\n]/g, "")
- .split(";")
- .map((query) => query.trim())
- .join(";\n");
- await c.env.DB.exec(query);
-
- const version = await utils.getSetting(c, CONSTANTS.DB_VERSION_KEY);
- if (version) {
- return c.json({ message: "Database already initialized" });
- }
- await utils.saveSetting(c, CONSTANTS.DB_VERSION_KEY, CONSTANTS.DB_VERSION);
- return c.json({ message: "Database initialized" });
- },
- migrate: async (c: Context) => {
- const version = await utils.getSetting(c, CONSTANTS.DB_VERSION_KEY);
- if (version != CONSTANTS.DB_VERSION) {
- // TODO: Perform migration logic here
-
- // Update the version in the settings table
- await utils.saveSetting(c, CONSTANTS.DB_VERSION_KEY, CONSTANTS.DB_VERSION);
- return c.json({
- success: true,
- message: "Database migrated"
- });
- }
- return c.json({
- success: true,
- message: "Database does not need migration"
- });
- },
- getVersion: async (c: Context) => {
- const version = await utils.getSetting(c, CONSTANTS.DB_VERSION_KEY);
- return c.json({
- need_initialization: !version,
- need_migration: version && version != CONSTANTS.DB_VERSION,
- current_db_version: version,
- code_db_version: CONSTANTS.DB_VERSION
- });
- },
-}
diff --git a/cloudflare_temp_email/worker/src/admin_api/index.ts b/cloudflare_temp_email/worker/src/admin_api/index.ts
deleted file mode 100644
index 472e1a8..0000000
--- a/cloudflare_temp_email/worker/src/admin_api/index.ts
+++ /dev/null
@@ -1,347 +0,0 @@
-import { Hono } from 'hono'
-import { Jwt } from 'hono/utils/jwt'
-
-import i18n from '../i18n'
-import { sendAdminInternalMail, getJsonSetting, saveSetting, getUserRoles } from '../utils'
-import { newAddress, handleListQuery } from '../common'
-import { CONSTANTS } from '../constants'
-import cleanup_api from './cleanup_api'
-import admin_user_api from './admin_user_api'
-import webhook_settings from './webhook_settings'
-import mail_webhook_settings from './mail_webhook_settings'
-import oauth2_settings from './oauth2_settings'
-import worker_config from './worker_config'
-import admin_mail_api from './admin_mail_api'
-import { sendMailbyAdmin } from './send_mail'
-import db_api from './db_api'
-import { EmailRuleSettings } from '../models'
-
-export const api = new Hono()
-
-api.get('/admin/address', async (c) => {
- const { limit, offset, query } = c.req.query();
- if (query) {
- return await handleListQuery(c,
- `SELECT a.*,`
- + ` (SELECT COUNT(*) FROM raw_mails WHERE address = a.name) AS mail_count,`
- + ` (SELECT COUNT(*) FROM sendbox WHERE address = a.name) AS send_count`
- + ` FROM address a`
- + ` where name like ?`,
- `SELECT count(*) as count FROM address where name like ?`,
- [`%${query}%`], limit, offset
- );
- }
- return await handleListQuery(c,
- `SELECT a.*,`
- + ` (SELECT COUNT(*) FROM raw_mails WHERE address = a.name) AS mail_count,`
- + ` (SELECT COUNT(*) FROM sendbox WHERE address = a.name) AS send_count`
- + ` FROM address a`,
- `SELECT count(*) as count FROM address`,
- [], limit, offset
- );
-})
-
-api.post('/admin/new_address', async (c) => {
- const { name, domain, enablePrefix } = await c.req.json();
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- if (!name) {
- return c.text("Please provide a name", 400)
- }
- try {
- const res = await newAddress(c, {
- name, domain, enablePrefix,
- checkLengthByConfig: false,
- addressPrefix: null,
- checkAllowDomains: false,
- enableCheckNameRegex: false,
- });
- return c.json(res);
- } catch (e) {
- return c.text(`${msgs.FailedCreateAddressMsg}: ${(e as Error).message}`, 400)
- }
-})
-
-api.delete('/admin/delete_address/:id', async (c) => {
- const { id } = c.req.param();
- const { success } = await c.env.DB.prepare(
- `DELETE FROM address WHERE id = ? `
- ).bind(id).run();
- if (!success) {
- return c.text("Failed to delete address", 500)
- }
- const { success: mailSuccess } = await c.env.DB.prepare(
- `DELETE FROM raw_mails WHERE address IN`
- + ` (select name from address where id = ?) `
- ).bind(id).run();
- if (!mailSuccess) {
- return c.text("Failed to delete mails", 500)
- }
- const { success: sendAccess } = await c.env.DB.prepare(
- `DELETE FROM address_sender WHERE address IN`
- + ` (select name from address where id = ?) `
- ).bind(id).run();
- const { success: usersAddressSuccess } = await c.env.DB.prepare(
- `DELETE FROM users_address WHERE address_id = ?`
- ).bind(id).run();
- return c.json({
- success: success && mailSuccess && sendAccess && usersAddressSuccess
- })
-})
-
-api.delete('/admin/clear_inbox/:id', async (c) => {
- const { id } = c.req.param();
- const { success: mailSuccess } = await c.env.DB.prepare(
- `DELETE FROM raw_mails WHERE address IN`
- + ` (select name from address where id = ?) `
- ).bind(id).run();
- if (!mailSuccess) {
- return c.text("Failed to clear inbox", 500)
- }
- return c.json({
- success: mailSuccess
- })
-})
-
-api.delete('/admin/clear_sent_items/:id', async (c) => {
- const { id } = c.req.param();
- const { success: sendboxSuccess } = await c.env.DB.prepare(
- `DELETE FROM sendbox WHERE address IN`
- + ` (select name from address where id = ?) `
- ).bind(id).run();
- if (!sendboxSuccess) {
- return c.text("Failed to clear sent items", 500)
- }
- return c.json({
- success: sendboxSuccess
- })
-})
-
-api.get('/admin/show_password/:id', async (c) => {
- const { id } = c.req.param();
- const name = await c.env.DB.prepare(
- `SELECT name FROM address WHERE id = ? `
- ).bind(id).first("name");
- const jwt = await Jwt.sign({
- address: name,
- address_id: id
- }, c.env.JWT_SECRET, "HS256")
- return c.json({
- jwt: jwt
- })
-})
-
-// mail api
-api.get('/admin/mails', admin_mail_api.getMails);
-api.get('/admin/mails_unknow', admin_mail_api.getUnknowMails);
-api.delete('/admin/mails/:id', admin_mail_api.deleteMail)
-
-api.get('/admin/address_sender', async (c) => {
- const { address, limit, offset } = c.req.query();
- if (address) {
- return await handleListQuery(c,
- `SELECT * FROM address_sender where address = ? `,
- `SELECT count(*) as count FROM address_sender where address = ? `,
- [address], limit, offset
- );
- }
- return await handleListQuery(c,
- `SELECT * FROM address_sender `,
- `SELECT count(*) as count FROM address_sender `,
- [], limit, offset
- );
-})
-
-api.post('/admin/address_sender', async (c) => {
- /* eslint-disable prefer-const */
- let { address, address_id, balance, enabled } = await c.req.json();
- /* eslint-enable prefer-const */
- if (!address_id) {
- return c.text("Invalid address_id", 400)
- }
- enabled = enabled ? 1 : 0;
- const { success } = await c.env.DB.prepare(
- `UPDATE address_sender SET enabled = ?, balance = ? WHERE id = ? `
- ).bind(enabled, balance, address_id).run();
- if (!success) {
- return c.text("Failed to update address sender", 500)
- }
- await sendAdminInternalMail(
- c, address, "Account Send Access Updated",
- `Your send access has been ${enabled ? "enabled" : "disabled"}, balance: ${balance}`
- );
- return c.json({
- success: success
- })
-})
-
-api.delete('/admin/address_sender/:id', async (c) => {
- const { id } = c.req.param();
- const { success } = await c.env.DB.prepare(
- `DELETE FROM address_sender WHERE id = ? `
- ).bind(id).run();
- return c.json({
- success: success
- })
-})
-
-api.get('/admin/sendbox', async (c) => {
- const { address, limit, offset } = c.req.query();
- if (address) {
- return await handleListQuery(c,
- `SELECT * FROM sendbox where address = ? `,
- `SELECT count(*) as count FROM sendbox where address = ? `,
- [address], limit, offset
- );
- }
- return await handleListQuery(c,
- `SELECT * FROM sendbox `,
- `SELECT count(*) as count FROM sendbox `,
- [], limit, offset
- );
-})
-
-api.delete('/admin/sendbox/:id', async (c) => {
- const { id } = c.req.param();
- const { success } = await c.env.DB.prepare(
- `DELETE FROM sendbox WHERE id = ? `
- ).bind(id).run();
- return c.json({
- success: success
- })
-})
-
-api.get('/admin/statistics', async (c) => {
- const { count: mailCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM raw_mails`
- ).first<{ count: number }>() || {};
- const { count: addressCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM address`
- ).first<{ count: number }>() || {};
- const { count: activeAddressCount7days } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM address where updated_at > datetime('now', '-7 day')`
- ).first<{ count: number }>() || {};
- const { count: activeAddressCount30days } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM address where updated_at > datetime('now', '-30 day')`
- ).first<{ count: number }>() || {};
- const { count: sendMailCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM sendbox`
- ).first<{ count: number }>() || {};
- const { count: userCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM users`
- ).first<{ count: number }>() || {};
- return c.json({
- mailCount: mailCount,
- addressCount: addressCount,
- activeAddressCount7days: activeAddressCount7days,
- activeAddressCount30days: activeAddressCount30days,
- userCount: userCount,
- sendMailCount: sendMailCount
- })
-});
-
-api.get('/admin/account_settings', async (c) => {
- try {
- const blockList = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
- const sendBlockList = await getJsonSetting(c, CONSTANTS.SEND_BLOCK_LIST_KEY);
- const verifiedAddressList = await getJsonSetting(c, CONSTANTS.VERIFIED_ADDRESS_LIST_KEY);
- const fromBlockList = c.env.KV ? await c.env.KV.get(CONSTANTS.EMAIL_KV_BLACK_LIST, 'json') : [];
- const emailRuleSettings = await getJsonSetting(c, CONSTANTS.EMAIL_RULE_SETTINGS_KEY);
- const noLimitSendAddressList = await getJsonSetting(c, CONSTANTS.NO_LIMIT_SEND_ADDRESS_LIST_KEY);
- return c.json({
- blockList: blockList || [],
- sendBlockList: sendBlockList || [],
- verifiedAddressList: verifiedAddressList || [],
- fromBlockList: fromBlockList || [],
- noLimitSendAddressList: noLimitSendAddressList || [],
- emailRuleSettings: emailRuleSettings || {}
- })
- } catch (error) {
- console.error(error);
- return c.json({})
- }
-})
-
-api.post('/admin/account_settings', async (c) => {
- /** @type {{ blockList: Array, sendBlockList: Array }} */
- const {
- blockList, sendBlockList, noLimitSendAddressList,
- verifiedAddressList, fromBlockList, emailRuleSettings
- } = await c.req.json();
- if (!blockList || !sendBlockList || !verifiedAddressList) {
- return c.text("Invalid blockList or sendBlockList", 400)
- }
- if (!c.env.SEND_MAIL && verifiedAddressList.length > 0) {
- return c.text("Please enable SEND_MAIL to use verifiedAddressList", 400)
- }
- await saveSetting(
- c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY,
- JSON.stringify(blockList)
- );
- await saveSetting(
- c, CONSTANTS.SEND_BLOCK_LIST_KEY,
- JSON.stringify(sendBlockList)
- );
- await saveSetting(
- c, CONSTANTS.VERIFIED_ADDRESS_LIST_KEY,
- JSON.stringify(verifiedAddressList)
- )
- if (fromBlockList?.length > 0 && !c.env.KV) {
- return c.text("Please enable KV to use fromBlockList", 400)
- }
- if (fromBlockList) {
- await c.env.KV.put(CONSTANTS.EMAIL_KV_BLACK_LIST, JSON.stringify(fromBlockList || []))
- }
- await saveSetting(
- c, CONSTANTS.NO_LIMIT_SEND_ADDRESS_LIST_KEY,
- JSON.stringify(noLimitSendAddressList || [])
- )
- await saveSetting(
- c, CONSTANTS.EMAIL_RULE_SETTINGS_KEY,
- JSON.stringify(emailRuleSettings || {})
- )
- return c.json({
- success: true
- })
-})
-
-// cleanup
-api.post('/admin/cleanup', cleanup_api.cleanup)
-api.get('/admin/auto_cleanup', cleanup_api.getCleanup)
-api.post('/admin/auto_cleanup', cleanup_api.saveCleanup)
-
-// user settings
-api.get('/admin/user_settings', admin_user_api.getSetting)
-api.post('/admin/user_settings', admin_user_api.saveSetting)
-api.get('/admin/users', admin_user_api.getUsers)
-api.delete('/admin/users/:user_id', admin_user_api.deleteUser)
-api.post('/admin/users', admin_user_api.createUser)
-api.post('/admin/users/:user_id/reset_password', admin_user_api.resetPassword)
-api.get('/admin/user_roles', async (c) => c.json(getUserRoles(c)))
-api.post('/admin/user_roles', admin_user_api.updateUserRoles)
-api.get('/admin/users/bind_address/:user_id', admin_user_api.getBindedAddresses)
-api.post('/admin/users/bind_address', admin_user_api.bindAddress)
-
-// user oauth2 settings
-api.get('/admin/user_oauth2_settings', oauth2_settings.getUserOauth2Settings)
-api.post('/admin/user_oauth2_settings', oauth2_settings.saveUserOauth2Settings)
-
-// webhook settings
-api.get("/admin/webhook/settings", webhook_settings.getWebhookSettings);
-api.post("/admin/webhook/settings", webhook_settings.saveWebhookSettings);
-
-// mail webhook settings
-api.get("/admin/mail_webhook/settings", mail_webhook_settings.getWebhookSettings);
-api.post("/admin/mail_webhook/settings", mail_webhook_settings.saveWebhookSettings);
-api.post("/admin/mail_webhook/test", mail_webhook_settings.testWebhookSettings);
-
-// worker config
-api.get("/admin/worker/configs", worker_config.getConfig);
-
-// send mail by admin
-api.post("/admin/send_mail", sendMailbyAdmin);
-
-// db api
-api.get('admin/db_version', db_api.getVersion);
-api.post('admin/db_initialize', db_api.initialize);
-api.post('admin/db_migration', db_api.migrate);
diff --git a/cloudflare_temp_email/worker/src/admin_api/mail_webhook_settings.ts b/cloudflare_temp_email/worker/src/admin_api/mail_webhook_settings.ts
deleted file mode 100644
index 71b55c1..0000000
--- a/cloudflare_temp_email/worker/src/admin_api/mail_webhook_settings.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Context } from "hono";
-import { CONSTANTS } from "../constants";
-import { WebhookSettings } from "../models";
-import { commonParseMail, sendWebhook } from "../common";
-
-async function getWebhookSettings(c: Context): Promise {
- const settings = await c.env.KV.get(
- CONSTANTS.WEBHOOK_KV_ADMIN_MAIL_SETTINGS_KEY, "json"
- ) || new WebhookSettings();
- return c.json(settings);
-}
-
-async function saveWebhookSettings(c: Context): Promise {
- const settings = await c.req.json();
- await c.env.KV.put(
- CONSTANTS.WEBHOOK_KV_ADMIN_MAIL_SETTINGS_KEY,
- JSON.stringify(settings));
- return c.json({ success: true })
-}
-
-async function testWebhookSettings(c: Context): Promise {
- const settings = await c.req.json();
- // random raw email
- const { id: mailId, raw } = await c.env.DB.prepare(
- `SELECT id, raw FROM raw_mails ORDER BY RANDOM() LIMIT 1`
- ).first<{ id: string, raw: string }>() || {};
- const parsedEmailContext: ParsedEmailContext = { rawEmail: raw || "" };
- const parsedEmail = await commonParseMail(parsedEmailContext);
- const res = await sendWebhook(settings, {
- id: mailId || "0",
- url: c.env.FRONTEND_URL ? `${c.env.FRONTEND_URL}?mail_id=${mailId}` : "",
- from: parsedEmail?.sender || "test@test.com",
- to: "admin@test.com",
- subject: parsedEmail?.subject || "test subject",
- raw: raw || "test raw email",
- parsedText: parsedEmail?.text || "test parsed text",
- parsedHtml: parsedEmail?.html || "test parsed html"
- });
- if (!res.success) {
- return c.text(res.message || "send webhook error", 400);
- }
- return c.json({ success: true });
-}
-
-export default {
- getWebhookSettings,
- saveWebhookSettings,
- testWebhookSettings,
-}
diff --git a/cloudflare_temp_email/worker/src/admin_api/oauth2_settings.ts b/cloudflare_temp_email/worker/src/admin_api/oauth2_settings.ts
deleted file mode 100644
index 64aa735..0000000
--- a/cloudflare_temp_email/worker/src/admin_api/oauth2_settings.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Context } from 'hono';
-
-import { CONSTANTS } from '../constants';
-import { UserOauth2Settings } from "../models";
-import { getJsonSetting, saveSetting } from '../utils';
-
-async function getUserOauth2Settings(c: Context): Promise {
- const settings = await getJsonSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY);
- return c.json(settings || []);
-}
-
-async function saveUserOauth2Settings(c: Context): Promise {
- const settings = await c.req.json();
- for (const setting of settings) {
- if (!setting.name || !setting.clientID || !setting.clientSecret
- || !setting.authorizationURL || !setting.accessTokenURL
- || !setting.accessTokenFormat
- || !setting.userInfoURL || !setting.redirectURL
- || !setting.userEmailKey || !setting.scope) {
- return c.text(`${setting.name} is missing required fields`, 400);
- }
- if (setting.enableMailAllowList && (setting.mailAllowList?.length || 0) < 1) {
- return c.text(`${setting.name} is missing mail allow list`, 400);
- }
- }
- await saveSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY, JSON.stringify(settings));
- return c.json({ success: true })
-}
-
-export default {
- getUserOauth2Settings,
- saveUserOauth2Settings,
-}
diff --git a/cloudflare_temp_email/worker/src/admin_api/send_mail.ts b/cloudflare_temp_email/worker/src/admin_api/send_mail.ts
deleted file mode 100644
index 41fa5a8..0000000
--- a/cloudflare_temp_email/worker/src/admin_api/send_mail.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Context } from "hono";
-import { sendMail } from "../mails_api/send_mail_api";
-
-export const sendMailbyAdmin = async (c: Context) => {
- const {
- from_name, from_mail,
- to_mail, to_name,
- subject, content, is_html
- } = await c.req.json();
- await sendMail(c, from_mail, {
- from_name: from_name,
- to_name: to_name,
- to_mail: to_mail,
- subject: subject,
- content: content,
- is_html: is_html,
- }, {
- isAdmin: true
- })
- return c.json({ status: "ok" });
-}
diff --git a/cloudflare_temp_email/worker/src/admin_api/webhook_settings.ts b/cloudflare_temp_email/worker/src/admin_api/webhook_settings.ts
deleted file mode 100644
index 3b415f9..0000000
--- a/cloudflare_temp_email/worker/src/admin_api/webhook_settings.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Context } from "hono";
-import { CONSTANTS } from "../constants";
-import { AdminWebhookSettings } from "../models";
-
-async function getWebhookSettings(c: Context): Promise {
- const settings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json");
- return c.json(settings || new AdminWebhookSettings(false, []));
-}
-
-async function saveWebhookSettings(c: Context): Promise {
- const settings = await c.req.json();
- await c.env.KV.put(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, JSON.stringify(settings));
- return c.json({ success: true })
-}
-
-export default {
- getWebhookSettings,
- saveWebhookSettings,
-}
diff --git a/cloudflare_temp_email/worker/src/admin_api/worker_config.ts b/cloudflare_temp_email/worker/src/admin_api/worker_config.ts
deleted file mode 100644
index 7614350..0000000
--- a/cloudflare_temp_email/worker/src/admin_api/worker_config.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { Context } from 'hono';
-
-import utils from '../utils';
-import { CONSTANTS } from '../constants';
-import { isS3Enabled } from '../mails_api/s3_attachment';
-
-export default {
- getConfig: async (c: Context) => {
- return c.json({
- "DEFAULT_LANG": c.env.DEFAULT_LANG,
- "TITLE": c.env.TITLE,
- "HAS_PASSWORD": utils.getPasswords(c).length,
- "HAS_ADMIN_PASSWORDS": utils.getAdminPasswords(c).length,
- "ANNOUNCEMENT": utils.getStringValue(c.env.ANNOUNCEMENT),
- "ALWAYS_SHOW_ANNOUNCEMENT": utils.getBooleanValue(c.env.ALWAYS_SHOW_ANNOUNCEMENT),
-
- "PREFIX": utils.getStringValue(c.env.PREFIX),
- "ADDRESS_CHECK_REGEX": utils.getStringValue(c.env.ADDRESS_CHECK_REGEX),
- "ADDRESS_REGEX": utils.getStringValue(c.env.ADDRESS_REGEX),
- "MIN_ADDRESS_LEN": utils.getIntValue(c.env.MIN_ADDRESS_LEN, 1),
- "MAX_ADDRESS_LEN": utils.getIntValue(c.env.MAX_ADDRESS_LEN, 30),
-
- "FORWARD_ADDRESS_LIST": utils.getStringArray(c.env.FORWARD_ADDRESS_LIST),
- "SUBDOMAIN_FORWARD_ADDRESS_LIST": utils.getJsonObjectValue(c.env.SUBDOMAIN_FORWARD_ADDRESS_LIST),
- "DEFAULT_DOMAINS": utils.getDefaultDomains(c),
- "DOMAINS": utils.getDomains(c),
- "DOMAIN_LABELS": utils.getStringArray(c.env.DOMAIN_LABELS),
-
- "HAS_JWT_SECRET": !!utils.getStringValue(c.env.JWT_SECRET),
-
- "ADMIN_USER_ROLE": utils.getStringValue(c.env.ADMIN_USER_ROLE),
- "USER_DEFAULT_ROLE": utils.getStringValue(c.env.USER_DEFAULT_ROLE),
- "USER_ROLES": utils.getUserRoles(c),
- "NO_LIMIT_SEND_ROLE": utils.getSplitStringListValue(c.env.NO_LIMIT_SEND_ROLE),
-
- "ADMIN_CONTACT": c.env.ADMIN_CONTACT,
- "ENABLE_USER_CREATE_EMAIL": utils.getBooleanValue(c.env.ENABLE_USER_CREATE_EMAIL),
- "DISABLE_ANONYMOUS_USER_CREATE_EMAIL": utils.getBooleanValue(c.env.DISABLE_ANONYMOUS_USER_CREATE_EMAIL),
- "ENABLE_USER_DELETE_EMAIL": utils.getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL),
- "ENABLE_AUTO_REPLY": utils.getBooleanValue(c.env.ENABLE_AUTO_REPLY),
- "COPYRIGHT": c.env.COPYRIGHT,
- "ENABLE_WEBHOOK": utils.getBooleanValue(c.env.ENABLE_WEBHOOK),
- "S3_ENABLED": isS3Enabled(c),
- "VERSION": CONSTANTS.VERSION,
- "DISABLE_SHOW_GITHUB": !utils.getBooleanValue(c.env.DISABLE_SHOW_GITHUB),
- "DISABLE_ADMIN_PASSWORD_CHECK": utils.getBooleanValue(c.env.DISABLE_ADMIN_PASSWORD_CHECK),
- "ENABLE_CHECK_JUNK_MAIL": utils.getBooleanValue(c.env.ENABLE_CHECK_JUNK_MAIL),
- "JUNK_MAIL_CHECK_LIST": utils.getStringArray(c.env.JUNK_MAIL_CHECK_LIST),
- "JUNK_MAIL_FORCE_PASS_LIST": utils.getStringArray(c.env.JUNK_MAIL_FORCE_PASS_LIST),
-
- "REMOVE_EXCEED_SIZE_ATTACHMENT": utils.getBooleanValue(c.env.REMOVE_EXCEED_SIZE_ATTACHMENT),
- "REMOVE_ALL_ATTACHMENT": utils.getBooleanValue(c.env.REMOVE_ALL_ATTACHMENT),
-
- "ENABLE_ANOTHER_WORKER": utils.getBooleanValue(c.env.ENABLE_ANOTHER_WORKER),
- "ANOTHER_WORKER_LIST": utils.getAnotherWorkerList(c),
- })
- }
-}
diff --git a/cloudflare_temp_email/worker/src/commom_api.ts b/cloudflare_temp_email/worker/src/commom_api.ts
deleted file mode 100644
index 2d6520a..0000000
--- a/cloudflare_temp_email/worker/src/commom_api.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { Hono } from 'hono'
-
-import utils from './utils';
-import { CONSTANTS } from './constants';
-import { isS3Enabled } from './mails_api/s3_attachment';
-
-const api = new Hono
-
-api.get('/open_api/settings', async (c) => {
- // check header x-custom-auth
- let needAuth = false;
- const passwords = utils.getPasswords(c);
- if (passwords && passwords.length > 0) {
- const auth = c.req.raw.headers.get("x-custom-auth");
- needAuth = !auth || !passwords.includes(auth);
- }
-
- return c.json({
- "title": c.env.TITLE,
- "announcement": utils.getStringValue(c.env.ANNOUNCEMENT),
- "alwaysShowAnnouncement": utils.getBooleanValue(c.env.ALWAYS_SHOW_ANNOUNCEMENT),
- "prefix": utils.getStringValue(c.env.PREFIX),
- "addressRegex": utils.getStringValue(c.env.ADDRESS_REGEX),
- "minAddressLen": utils.getIntValue(c.env.MIN_ADDRESS_LEN, 1),
- "maxAddressLen": utils.getIntValue(c.env.MAX_ADDRESS_LEN, 30),
- "defaultDomains": utils.getDefaultDomains(c),
- "domains": utils.getDomains(c),
- "domainLabels": utils.getStringArray(c.env.DOMAIN_LABELS),
- "needAuth": needAuth,
- "adminContact": c.env.ADMIN_CONTACT,
- "enableUserCreateEmail": utils.getBooleanValue(c.env.ENABLE_USER_CREATE_EMAIL),
- "disableAnonymousUserCreateEmail": utils.getBooleanValue(c.env.DISABLE_ANONYMOUS_USER_CREATE_EMAIL),
- "disableCustomAddressName": utils.getBooleanValue(c.env.DISABLE_CUSTOM_ADDRESS_NAME),
- "enableUserDeleteEmail": utils.getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL),
- "enableAutoReply": utils.getBooleanValue(c.env.ENABLE_AUTO_REPLY),
- "enableIndexAbout": utils.getBooleanValue(c.env.ENABLE_INDEX_ABOUT),
- "copyright": c.env.COPYRIGHT,
- "cfTurnstileSiteKey": c.env.CF_TURNSTILE_SITE_KEY,
- "enableWebhook": utils.getBooleanValue(c.env.ENABLE_WEBHOOK),
- "isS3Enabled": isS3Enabled(c),
- "version": CONSTANTS.VERSION,
- "showGithub": !utils.getBooleanValue(c.env.DISABLE_SHOW_GITHUB),
- "disableAdminPasswordCheck": utils.getBooleanValue(c.env.DISABLE_ADMIN_PASSWORD_CHECK)
- });
-})
-
-export { api }
diff --git a/cloudflare_temp_email/worker/src/common.ts b/cloudflare_temp_email/worker/src/common.ts
deleted file mode 100644
index 75705ae..0000000
--- a/cloudflare_temp_email/worker/src/common.ts
+++ /dev/null
@@ -1,566 +0,0 @@
-import { Context } from 'hono';
-import { Jwt } from 'hono/utils/jwt'
-
-import { getBooleanValue, getDomains, getStringValue, getIntValue, getUserRoles, getDefaultDomains, getJsonSetting, getAnotherWorkerList } from './utils';
-import { unbindTelegramByAddress } from './telegram_api/common';
-import { CONSTANTS } from './constants';
-import { AdminWebhookSettings, WebhookMail, WebhookSettings } from './models';
-
-const DEFAULT_NAME_REGEX = /[^a-z0-9]/g;
-
-export const generateRandomName = (c: Context): string => {
- // name min length min 1
- const minLength = Math.max(
- getIntValue(c.env.MIN_ADDRESS_LEN, 1),
- 1
- );
- // name max length min 1
- const maxLength = Math.max(
- getIntValue(c.env.MAX_ADDRESS_LEN, 30),
- 1
- );
-
- // Build full name recursively until minimum length is reached
- const buildName = (currentName: string = ""): string => {
- return currentName.length >= minLength
- ? currentName
- : buildName(currentName + Math.random().toString(36).substring(2, 15));
- };
-
- const fullName = buildName();
-
- // Return truncated to max length
- return fullName.substring(0, Math.min(fullName.length, maxLength));
-};
-
-const checkNameRegex = (c: Context, name: string) => {
- let error = null;
- try {
- const regexStr = getStringValue(c.env.ADDRESS_CHECK_REGEX);
- if (!regexStr) return;
- const regex = new RegExp(regexStr);
- if (!regex.test(name)) {
- error = new Error(`Name not match regex: /${regexStr}/`);
- }
- }
- catch (e) {
- console.error("Failed to check address regex", e);
- }
- if (error) {
- throw error;
- }
-}
-
-const getNameRegex = (c: Context): RegExp => {
- try {
- const regex = getStringValue(c.env.ADDRESS_REGEX);
- if (!regex) {
- return DEFAULT_NAME_REGEX;
- }
- return new RegExp(regex, 'g');
- }
- catch (e) {
- console.error("Failed to get address regex", e);
- }
- return DEFAULT_NAME_REGEX;
-}
-
-export async function updateAddressUpdatedAt(
- c: Context,
- address: string | undefined | null
-): Promise {
- if (!address) {
- return;
- }
- // update address updated_at
- try {
- await c.env.DB.prepare(
- `UPDATE address SET updated_at = datetime('now') where name = ?`
- ).bind(address).run();
- } catch (e) {
- console.warn("Failed to update address updated_at", e);
- }
-}
-
-export const newAddress = async (
- c: Context,
- {
- name,
- domain,
- enablePrefix,
- checkLengthByConfig = true,
- addressPrefix = null,
- checkAllowDomains = true,
- enableCheckNameRegex = true,
- }: {
- name: string, domain: string | undefined | null,
- enablePrefix: boolean,
- checkLengthByConfig?: boolean,
- addressPrefix?: string | undefined | null,
- checkAllowDomains?: boolean,
- enableCheckNameRegex?: boolean,
- }
-): Promise<{ address: string, jwt: string }> => {
- // trim whitespace and remove special characters
- name = name.trim().replace(getNameRegex(c), '')
- // check name
- if (enableCheckNameRegex) {
- await checkNameBlockList(c, name);
- checkNameRegex(c, name);
- }
- // name min length min 1
- const minAddressLength = Math.max(
- checkLengthByConfig ? getIntValue(c.env.MIN_ADDRESS_LEN, 1) : 1,
- 1
- );
- // name max length min 1
- const maxAddressLength = Math.max(
- checkLengthByConfig ? getIntValue(c.env.MAX_ADDRESS_LEN, 30) : 30,
- 1
- );
- // check name length
- if (name.length < minAddressLength) {
- throw new Error(`Name too short (min ${minAddressLength})`);
- }
- if (name.length > maxAddressLength) {
- throw new Error(`Name too long (max ${maxAddressLength})`);
- }
- // create address with prefix
- if (typeof addressPrefix === "string") {
- name = addressPrefix.trim() + name;
- } else if (enablePrefix) {
- name = getStringValue(c.env.PREFIX).trim() + name;
- }
- // check domain
- const allowDomains = checkAllowDomains ? await getAllowDomains(c) : getDomains(c);
- // if domain is not set, select domain based on environment configuration
- if (!domain && allowDomains.length > 0) {
- const createAddressDefaultDomainFirst = getBooleanValue(c.env.CREATE_ADDRESS_DEFAULT_DOMAIN_FIRST);
- if (createAddressDefaultDomainFirst) {
- domain = allowDomains[0];
- } else {
- domain = allowDomains[Math.floor(Math.random() * allowDomains.length)];
- }
- }
- // check domain is valid
- if (!domain || !allowDomains.includes(domain)) {
- throw new Error("Invalid domain")
- }
- // create address
- name = name + "@" + domain;
- try {
- const { success } = await c.env.DB.prepare(
- `INSERT INTO address(name) VALUES(?)`
- ).bind(name).run();
- if (!success) {
- throw new Error("Failed to create address")
- }
- await updateAddressUpdatedAt(c, name);
- } catch (e) {
- const message = (e as Error).message;
- if (message && message.includes("UNIQUE")) {
- throw new Error("Address already exists")
- }
- throw new Error("Failed to create address")
- }
- const address_id = await c.env.DB.prepare(
- `SELECT id FROM address where name = ?`
- ).bind(name).first("id");
- // create jwt
- const jwt = await Jwt.sign({
- address: name,
- address_id: address_id
- }, c.env.JWT_SECRET, "HS256")
- return {
- jwt: jwt,
- address: name,
- }
-}
-
-const checkNameBlockList = async (
- c: Context, name: string
-): Promise => {
- // check name block list
- const blockList = [] as string[];
- try {
- const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
- blockList.push(...(value || []));
- } catch (error) {
- console.error(error);
- }
- if (blockList.some((item) => name.includes(item))) {
- throw new Error(`Name[${name}]is blocked`);
- }
-}
-
-export const cleanup = async (
- c: Context,
- cleanType: string | undefined | null,
- cleanDays: number | undefined | null
-): Promise => {
- if (!cleanType || typeof cleanDays !== 'number' || cleanDays < 0 || cleanDays > 1000) {
- throw new Error("Invalid cleanType or cleanDays")
- }
- console.log(`Cleanup ${cleanType} before ${cleanDays} days`);
- switch (cleanType) {
- case "inactiveAddress":
- await batchDeleteAddressWithData(
- c,
- `updated_at < datetime('now', '-${cleanDays} day')`
- )
- break;
- case "addressCreated":
- await batchDeleteAddressWithData(
- c,
- `created_at < datetime('now', '-${cleanDays} day')`
- )
- break;
- case "mails":
- await c.env.DB.prepare(`
- DELETE FROM raw_mails WHERE created_at < datetime('now', '-${cleanDays} day')`
- ).run();
- break;
- case "mails_unknow":
- await c.env.DB.prepare(`
- DELETE FROM raw_mails WHERE address NOT IN
- (select name from address) AND created_at < datetime('now', '-${cleanDays} day')`
- ).run();
- break;
- case "sendbox":
- await c.env.DB.prepare(`
- DELETE FROM sendbox WHERE created_at < datetime('now', '-${cleanDays} day')`
- ).run();
- break;
- default:
- throw new Error("Invalid cleanType")
- }
- return true;
-}
-
-const batchDeleteAddressWithData = async (
- c: Context,
- addressQueryCondition: string,
-): Promise => {
- await c.env.DB.prepare(
- `DELETE FROM raw_mails WHERE address IN ( ` +
- `SELECT name FROM address WHERE ${addressQueryCondition})`
- ).run();
- await c.env.DB.prepare(
- `DELETE FROM sendbox WHERE address IN ( ` +
- `SELECT name FROM address WHERE ${addressQueryCondition})`
- ).run();
- await c.env.DB.prepare(
- `DELETE FROM auto_reply_mails WHERE address IN ( ` +
- `SELECT name FROM address WHERE ${addressQueryCondition})`
- ).run();
- await c.env.DB.prepare(
- `DELETE FROM address_sender WHERE address IN ( ` +
- `SELECT name FROM address WHERE ${addressQueryCondition})`
- ).run();
- await c.env.DB.prepare(
- `DELETE FROM users_address WHERE address_id IN ( ` +
- `SELECT id FROM address WHERE ${addressQueryCondition})`
- ).run();
- // delete address
- await c.env.DB.prepare(`
- DELETE FROM address WHERE ${addressQueryCondition}`
- ).run();
- return true;
-}
-
-
-export const deleteAddressWithData = async (
- c: Context,
- address: string | undefined | null,
- address_id: number | undefined | null
-): Promise => {
- if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
- throw new Error("Delete email is disabled")
- }
- if (!address && !address_id) {
- throw new Error("Address or address_id required")
- }
- // get address_id or address
- if (!address_id) {
- address_id = await c.env.DB.prepare(
- `SELECT id FROM address where name = ?`
- ).bind(address).first("id");
- } else if (!address) {
- address = await c.env.DB.prepare(
- `SELECT name FROM address where id = ?`
- ).bind(address_id).first("name");
- }
- // check address again
- if (!address || !address_id) {
- throw new Error("Can't find address");
- }
- // unbind telegram
- await unbindTelegramByAddress(c, address);
- // delete address and related data
- const { success: mailSuccess } = await c.env.DB.prepare(
- `DELETE FROM raw_mails WHERE address = ? `
- ).bind(address).run();
- const { success: sendAccess } = await c.env.DB.prepare(
- `DELETE FROM address_sender WHERE address = ? `
- ).bind(address).run();
- const { success: sendboxSuccess } = await c.env.DB.prepare(
- `DELETE FROM sendbox WHERE address = ? `
- ).bind(address).run();
- const { success: addressSuccess } = await c.env.DB.prepare(
- `DELETE FROM users_address WHERE address_id = ? `
- ).bind(address_id).run();
- const { success: autoReplySuccess } = await c.env.DB.prepare(
- `DELETE FROM auto_reply_mails WHERE address = ? `
- ).bind(address).run();
- const { success } = await c.env.DB.prepare(
- `DELETE FROM address WHERE name = ? `
- ).bind(address).run();
- if (!success || !mailSuccess || !sendboxSuccess || !addressSuccess || !sendAccess || !autoReplySuccess) {
- throw new Error("Failed to delete address")
- }
- return true;
-}
-
-export const handleListQuery = async (
- c: Context,
- query: string, countQuery: string, params: string[],
- limit: string | number | undefined | null,
- offset: string | number | undefined | null
-): Promise => {
- if (typeof limit === "string") {
- limit = parseInt(limit);
- }
- if (typeof offset === "string") {
- offset = parseInt(offset);
- }
- if (!limit || limit < 0 || limit > 100) {
- return c.text("Invalid limit", 400)
- }
- if (offset == null || offset == undefined || offset < 0) {
- return c.text("Invalid offset", 400)
- }
- const resultsQuery = `${query} order by id desc limit ? offset ?`;
- const { results } = await c.env.DB.prepare(resultsQuery).bind(
- ...params, limit, offset
- ).all();
- const count = offset == 0 ? await c.env.DB.prepare(
- countQuery
- ).bind(...params).first("count") : 0;
- return c.json({ results, count });
-}
-
-
-export const commonParseMail = async (parsedEmailContext: ParsedEmailContext): Promise<{
- sender: string,
- subject: string,
- text: string,
- html: string,
- headers?: Record[]
-} | undefined> => {
- // check parsed email context is valid
- if (!parsedEmailContext || !parsedEmailContext.rawEmail) {
- return undefined;
- }
- // return parsed email if already parsed
- if (parsedEmailContext.parsedEmail) {
- return parsedEmailContext.parsedEmail;
- }
- const raw_mail = parsedEmailContext.rawEmail;
- // TODO: WASM parse email
- // try {
- // const { parse_message_wrapper } = await import('mail-parser-wasm-worker');
-
- // const parsedEmail = parse_message_wrapper(raw_mail);
- // parsedEmailContext.parsedEmail = {
- // sender: parsedEmail.sender || "",
- // subject: parsedEmail.subject || "",
- // text: parsedEmail.text || "",
- // headers: parsedEmail.headers?.map(
- // (header) => ({ key: header.key, value: header.value })
- // ) || [],
- // html: parsedEmail.body_html || "",
- // };
- // return parsedEmailContext.parsedEmail;
- // } catch (e) {
- // console.error("Failed use mail-parser-wasm-worker to parse email", e);
- // }
- try {
- const { default: PostalMime } = await import('postal-mime');
- const parsedEmail = await PostalMime.parse(raw_mail);
- parsedEmailContext.parsedEmail = {
- sender: parsedEmail.from ? `${parsedEmail.from.name} <${parsedEmail.from.address}>` : "",
- subject: parsedEmail.subject || "",
- text: parsedEmail.text || "",
- html: parsedEmail.html || "",
- headers: parsedEmail.headers || [],
- };
- return parsedEmailContext.parsedEmail;
- }
- catch (e) {
- console.error("Failed use PostalMime to parse email", e);
- }
- return undefined;
-}
-
-export const commonGetUserRole = async (
- c: Context, user_id: number
-): Promise => {
- const user_roles = getUserRoles(c);
- const role_text = await c.env.DB.prepare(
- `SELECT role_text FROM user_roles where user_id = ?`
- ).bind(user_id).first("role_text");
- return role_text ? user_roles.find((r) => r.role === role_text) : null;
-}
-
-export const getAddressPrefix = async (c: Context): Promise => {
- const user = c.get("userPayload");
- if (!user) {
- return getStringValue(c.env.PREFIX);
- }
- const user_role = await commonGetUserRole(c, user.user_id);
- if (typeof user_role?.prefix === "string") {
- return user_role.prefix;
- }
- return getStringValue(c.env.PREFIX);
-}
-
-export const getAllowDomains = async (c: Context): Promise => {
- const user = c.get("userPayload");
- if (!user) {
- return getDefaultDomains(c);
- }
- const user_role = await commonGetUserRole(c, user.user_id);
- return user_role?.domains || getDefaultDomains(c);;
-}
-
-export async function sendWebhook(
- settings: WebhookSettings, formatMap: WebhookMail
-): Promise<{ success: boolean, message?: string }> {
- // send webhook
- let body = settings.body;
- for (const key of Object.keys(formatMap)) {
- body = body.replace(
- new RegExp(`\\$\\{${key}\\}`, "g"),
- JSON.stringify(
- formatMap[key as keyof WebhookMail]
- ).replace(/^"(.*)"$/, '$1')
- );
- }
- const response = await fetch(settings.url, {
- method: settings.method,
- headers: JSON.parse(settings.headers),
- body: body
- });
- if (!response.ok) {
- console.log("send webhook error", settings.url, settings.method, settings.headers, body);
- console.log("send webhook error", response.status, response.statusText);
- return { success: false, message: `send webhook error: ${response.status} ${response.statusText}` };
- }
- return { success: true }
-}
-
-export async function triggerWebhook(
- c: Context,
- address: string,
- parsedEmailContext: ParsedEmailContext,
- message_id: string | null
-): Promise {
- if (!c.env.KV || !getBooleanValue(c.env.ENABLE_WEBHOOK)) {
- return
- }
- const webhookList: WebhookSettings[] = []
-
- // admin mail webhook
- const adminMailWebhookSettings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_ADMIN_MAIL_SETTINGS_KEY, "json");
- if (adminMailWebhookSettings?.enabled) {
- webhookList.push(adminMailWebhookSettings)
- }
-
- // user mail webhook
- const adminSettings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json");
- if (!adminSettings?.enableAllowList || adminSettings?.allowList.includes(address)) {
- const settings = await c.env.KV.get(
- `${CONSTANTS.WEBHOOK_KV_USER_SETTINGS_KEY}:${address}`, "json"
- );
- if (settings?.enabled) {
- webhookList.push(settings)
- }
- }
-
- // no webhook
- if (webhookList.length === 0) {
- return
- }
- const mailId = await c.env.DB.prepare(
- `SELECT id FROM raw_mails where address = ? and message_id = ?`
- ).bind(address, message_id).first("id");
-
- const parsedEmail = await commonParseMail(parsedEmailContext);
- const webhookMail = {
- id: mailId || "",
- url: c.env.FRONTEND_URL ? `${c.env.FRONTEND_URL}?mail_id=${mailId}` : "",
- from: parsedEmail?.sender || "",
- to: address,
- subject: parsedEmail?.subject || "",
- raw: parsedEmailContext.rawEmail || "",
- parsedText: parsedEmail?.text || "",
- parsedHtml: parsedEmail?.html || ""
- }
- for (const settings of webhookList) {
- const res = await sendWebhook(settings, webhookMail);
- if (!res.success) {
- console.error(res.message);
- }
- }
-}
-
-export async function triggerAnotherWorker(
- c: Context,
- rpcEmailMessage: RPCEmailMessage,
- parsedText: string | undefined | null
-): Promise {
- if (!parsedText) {
- return;
- }
-
- const anotherWorkerList: AnotherWorker[] = getAnotherWorkerList(c);
- if (!getBooleanValue(c.env.ENABLE_ANOTHER_WORKER) || anotherWorkerList.length === 0) {
- return;
- }
-
- const parsedTextLowercase: string = parsedText.toLowerCase();
- for (const worker of anotherWorkerList) {
-
- const keywords = worker?.keywords ?? [];
- const bindingName = worker?.binding ?? "";
- const methodName = worker.method ?? "rpcEmail";
-
- const serviceBinding = (c.env as any)[bindingName] ?? {};
- const method = serviceBinding[methodName];
-
- if (!method || typeof method !== "function") {
- console.log(`method = ${methodName} not found or not function`);
- continue;
- }
-
- if (!keywords.some(keyword => keyword && parsedTextLowercase.includes(keyword.toLowerCase()))) {
- console.log(`worker.binding = ${bindingName} not match keywords, parsedText = ${parsedText}`);
- continue;
- }
- try {
- const bodyObj = { ...rpcEmailMessage } as any;
- if (bodyObj.headers && typeof bodyObj.headers.forEach === "function") {
- const headerObj: any = {}
- bodyObj.headers.forEach((value: string, key: string) => {
- headerObj[key] = value;
- });
- bodyObj.headers = headerObj
- }
- const requestBody = JSON.stringify(bodyObj);
- console.log(`exec worker , binding = ${bindingName} , requestBody = ${requestBody}`);
- await method(requestBody);
- } catch (e1) {
- console.error(`execute method = ${methodName} error`, e1);
- }
- }
-}
diff --git a/cloudflare_temp_email/worker/src/constants.ts b/cloudflare_temp_email/worker/src/constants.ts
deleted file mode 100644
index 778f177..0000000
--- a/cloudflare_temp_email/worker/src/constants.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-export const CONSTANTS = {
- VERSION: 'v' + '1.0.5',
-
- // DB Version
- DB_VERSION_KEY: 'db_version',
- DB_VERSION: "v0.0.1",
-
- // DB settings
- ADDRESS_BLOCK_LIST_KEY: 'address_block_list',
- SEND_BLOCK_LIST_KEY: 'send_block_list',
- AUTO_CLEANUP_KEY: 'auto_cleanup',
- USER_SETTINGS_KEY: 'user_settings',
- OAUTH2_SETTINGS_KEY: 'oauth2_settings',
- VERIFIED_ADDRESS_LIST_KEY: 'verified_address_list',
- NO_LIMIT_SEND_ADDRESS_LIST_KEY: 'no_limit_send_address_list',
- EMAIL_RULE_SETTINGS_KEY: 'email_rule_settings',
-
- // KV
- TG_KV_PREFIX: "temp-mail-telegram",
- TG_KV_SETTINGS_KEY: "temp-mail-telegram-settings",
- WEBHOOK_KV_SETTINGS_KEY: "temp-mail-webhook-settings",
- WEBHOOK_KV_USER_SETTINGS_KEY: "temp-mail-webhook-user-settings",
- EMAIL_KV_BLACK_LIST: "temp-mail-email-black-list",
- WEBHOOK_KV_ADMIN_MAIL_SETTINGS_KEY: "temp-mail-webhook-admin-mail-settings",
-}
diff --git a/cloudflare_temp_email/worker/src/email/auto_reply.ts b/cloudflare_temp_email/worker/src/email/auto_reply.ts
deleted file mode 100644
index d694da5..0000000
--- a/cloudflare_temp_email/worker/src/email/auto_reply.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { createMimeMessage } from "mimetext";
-import { getBooleanValue } from "../utils";
-
-export const auto_reply = async (message: ForwardableEmailMessage, env: Bindings): Promise => {
- const message_id = message.headers.get("Message-ID");
- // auto reply email
- if (getBooleanValue(env.ENABLE_AUTO_REPLY) && message_id) {
- try {
- const results = await env.DB.prepare(
- `SELECT * FROM auto_reply_mails where address = ? and enabled = 1`
- ).bind(message.to).first>();
- if (results && results.source_prefix && message.from.startsWith(results.source_prefix)) {
- const msg = createMimeMessage();
- msg.setHeader("In-Reply-To", message_id);
- msg.setSender({
- name: results.name || results.address,
- addr: results.address
- });
- msg.setRecipient(message.from);
- msg.setSubject(results.subject || "Auto-reply");
- msg.addMessage({
- contentType: 'text/plain',
- data: results.message || "This is an auto-reply message, please reconact later."
- });
- const { EmailMessage } = await import('cloudflare:email');
- const replyMessage = new EmailMessage(
- message.to,
- message.from,
- msg.asRaw()
- );
- // @ts-ignore
- await message.reply(replyMessage);
- }
- } catch (error) {
- console.log("reply email error", error);
- }
- }
-}
diff --git a/cloudflare_temp_email/worker/src/email/black_list.ts b/cloudflare_temp_email/worker/src/email/black_list.ts
deleted file mode 100644
index 0898b6d..0000000
--- a/cloudflare_temp_email/worker/src/email/black_list.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { CONSTANTS } from "../constants";
-
-export const isBlocked = async (from: string, env: Bindings): Promise => {
- if (env.BLACK_LIST && env.BLACK_LIST.split(",").some(word => from.includes(word))) {
- return true;
- }
- if (!env.KV) {
- return false;
- }
- const blockList = await env.KV.get(CONSTANTS.EMAIL_KV_BLACK_LIST, 'json') || [];
- if (blockList.some(word => from.includes(word))) {
- return true;
- }
- return false;
-}
diff --git a/cloudflare_temp_email/worker/src/email/check_attachment.ts b/cloudflare_temp_email/worker/src/email/check_attachment.ts
deleted file mode 100644
index 3e59655..0000000
--- a/cloudflare_temp_email/worker/src/email/check_attachment.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { getBooleanValue } from "../utils";
-import { commonParseMail } from "../common";
-import { createMimeMessage } from "mimetext";
-
-export const remove_attachment_if_need = async (
- env: Bindings,
- parsedEmailContext: ParsedEmailContext,
- from_address: string,
- to_address: string,
- size: number
-): Promise => {
- // if configured, remove all attachment
- const removeAllAttachment = getBooleanValue(env.REMOVE_ALL_ATTACHMENT);
- // if attachment size > 2MB, remove attachment
- const removeExceedSizeAttachment = getBooleanValue(env.REMOVE_EXCEED_SIZE_ATTACHMENT) && size >= 2 * 1024 * 1024;
- const shouldRemoveAttachment = removeAllAttachment || removeExceedSizeAttachment;
- if (!shouldRemoveAttachment) return;
-
- const parsedEmail = await commonParseMail(parsedEmailContext);
- if (!parsedEmail) return;
-
- const msg = createMimeMessage();
- if (parsedEmail?.headers) {
- for (const header of parsedEmail.headers) {
- try {
- msg.setHeader(header["key"], header["value"]);
- } catch (error) {
- // ignore
- }
- }
- }
- msg.setSender({
- name: parsedEmail?.sender || from_address,
- addr: from_address
- });
- msg.setRecipient(to_address);
- msg.setSubject(parsedEmail?.subject || "Failed to parse email subject");
- if (parsedEmail?.html) {
- msg.addMessage({
- contentType: 'text/html',
- data: parsedEmail.html
- });
- }
- if (parsedEmail?.text) {
- msg.addMessage({
- contentType: 'text/plain',
- data: parsedEmail.text
- });
- }
- parsedEmailContext.rawEmail = msg.asRaw();
-}
diff --git a/cloudflare_temp_email/worker/src/email/check_junk.ts b/cloudflare_temp_email/worker/src/email/check_junk.ts
deleted file mode 100644
index af2b59f..0000000
--- a/cloudflare_temp_email/worker/src/email/check_junk.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { getBooleanValue, getStringArray } from "../utils";
-import { commonParseMail } from "../common";
-
-export const check_if_junk_mail = async (
- env: Bindings, address: string,
- parsedEmailContext: ParsedEmailContext,
- message_id: string | null
-): Promise => {
- if (!getBooleanValue(env.ENABLE_CHECK_JUNK_MAIL)) {
- return false;
- }
- const parsedEmail = await commonParseMail(parsedEmailContext);
- if (!parsedEmail?.headers) return false;
-
- const checkListWhenExist = getStringArray(env.JUNK_MAIL_CHECK_LIST);
- const forcePassList = getStringArray(env.JUNK_MAIL_FORCE_PASS_LIST);
- const passedList: string[] = [];
- const existList: string[] = [];
-
- const headers = parsedEmail.headers;
- for (const header of headers) {
- if (!header["key"]) continue;
- if (!header["value"]) continue;
-
- // check spf
- if (header["key"].toLowerCase() == "received-spf") {
- existList.push("spf");
- if (header["value"].toLowerCase().includes("pass")) {
- passedList.push("spf");
- }
- }
-
- // check dkim and dmarc
- if (header["key"].toLowerCase() == "authentication-results") {
- if (header["value"].toLowerCase().includes("dkim=")) {
- existList.push("dkim");
- if (header["value"].toLowerCase().includes("dkim=pass")) {
- passedList.push("dkim");
- }
- }
- if (header["value"].toLowerCase().includes("dmarc=")) {
- existList.push("dmarc");
- if (header["value"].toLowerCase().includes("dmarc=pass")) {
- passedList.push("dmarc");
- }
- }
- }
- }
- // check if all checkListWhenExist item passed when exist
- if (checkListWhenExist?.some(
- (checkName) => existList.includes(checkName.toLowerCase())
- && !passedList.includes(checkName.toLowerCase())
- )) {
- return true;
- }
-
- if (forcePassList?.length == 0) return false;
-
- // check force pass list
- return forcePassList.some(
- (checkName) => !passedList.includes(checkName.toLowerCase())
- );
-}
diff --git a/cloudflare_temp_email/worker/src/email/index.ts b/cloudflare_temp_email/worker/src/email/index.ts
deleted file mode 100644
index 523c918..0000000
--- a/cloudflare_temp_email/worker/src/email/index.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-import { Context } from "hono";
-
-import { getEnvStringList, getJsonObjectValue, getJsonSetting } from "../utils";
-import { sendMailToTelegram } from "../telegram_api";
-import { auto_reply } from "./auto_reply";
-import { isBlocked } from "./black_list";
-import { triggerWebhook, triggerAnotherWorker, commonParseMail } from "../common";
-import { check_if_junk_mail } from "./check_junk";
-import { remove_attachment_if_need } from "./check_attachment";
-import { EmailRuleSettings } from "../models";
-import { CONSTANTS } from "../constants";
-
-
-async function email(message: ForwardableEmailMessage, env: Bindings, ctx: ExecutionContext) {
- if (await isBlocked(message.from, env)) {
- message.setReject("Reject from address");
- console.log(`Reject message from ${message.from} to ${message.to}`);
- return;
- }
- const rawEmail = await new Response(message.raw).text();
- const parsedEmailContext: ParsedEmailContext = {
- rawEmail: rawEmail
- };
-
- // check if junk mail
- try {
- const is_junk = await check_if_junk_mail(env, message.to, parsedEmailContext, message.headers.get("Message-ID"));
- if (is_junk) {
- message.setReject("Junk mail");
- console.log(`Junk mail from ${message.from} to ${message.to}`);
- return;
- }
- } catch (error) {
- console.error("check junk mail error", error);
- }
-
- // check if unknown address mail
- try {
- const emailRuleSettings = await getJsonSetting(
- { env: env } as Context, CONSTANTS.EMAIL_RULE_SETTINGS_KEY
- );
- if (emailRuleSettings?.blockReceiveUnknowAddressEmail) {
- const db_address_id = await env.DB.prepare(
- `SELECT id FROM address where name = ? `
- ).bind(message.to).first("id");
- if (!db_address_id) {
- message.setReject("Unknown address");
- console.log(`Unknown address mail from ${message.from} to ${message.to}`);
- return;
- }
- }
- } catch (error) {
- console.error("check unknown address mail error", error);
- }
-
- // remove attachment if configured or size > 2MB
- try {
- await remove_attachment_if_need(env, parsedEmailContext, message.from, message.to, message.rawSize);
- } catch (error) {
- console.error("remove attachment error", error);
- }
-
- const message_id = message.headers.get("Message-ID");
- // save email
- try {
- const { success } = await env.DB.prepare(
- `INSERT INTO raw_mails (source, address, raw, message_id) VALUES (?, ?, ?, ?)`
- ).bind(
- message.from, message.to, parsedEmailContext.rawEmail, message_id
- ).run();
- if (!success) {
- message.setReject(`Failed save message to ${message.to}`);
- console.error(`Failed save message from ${message.from} to ${message.to}`);
- }
- }
- catch (error) {
- console.error("save email error", error);
- }
-
- // forward email
- try {
- const forwardAddressList = getEnvStringList(env.FORWARD_ADDRESS_LIST)
- for (const forwardAddress of forwardAddressList) {
- await message.forward(forwardAddress);
- }
- } catch (error) {
- console.error("forward email error", error);
- }
-
- // forward subdomain email
- try {
- // 遍历 FORWARD_ADDRESS_LIST
- const subdomainForwardAddressList = getJsonObjectValue(env.SUBDOMAIN_FORWARD_ADDRESS_LIST) || [];
- const emailRuleSettings = await getJsonSetting(
- { env: env } as Context, CONSTANTS.EMAIL_RULE_SETTINGS_KEY
- );
- // 合并两个配置, env 里的配置优先级更高
- const allSubdomainForwardAddressList = [
- ...(subdomainForwardAddressList || []),
- ...(emailRuleSettings?.emailForwardingList || []),
- ];
- for (const subdomainForwardAddress of allSubdomainForwardAddressList) {
- // 检查邮件是否匹配 domains
- if (subdomainForwardAddress.domains && subdomainForwardAddress.domains.length > 0) {
- for (const domain of subdomainForwardAddress.domains) {
- if (message.to.endsWith(domain) && subdomainForwardAddress.forward) {
- // 转发邮件
- await message.forward(subdomainForwardAddress.forward);
- // 支持多邮箱转发收件,不进行截止
- // break;
- }
- }
- } else {
- // 如果 domains 为空,则转发所有邮件
- await message.forward(subdomainForwardAddress.forward);
- }
- }
- } catch (error) {
- console.error("subdomain forward email error", error);
- }
-
- // send email to telegram
- try {
- await sendMailToTelegram(
- { env: env } as Context,
- message.to, parsedEmailContext, message_id);
- } catch (error) {
- console.error("send mail to telegram error", error);
- }
-
- // send webhook
- try {
- await triggerWebhook(
- { env: env } as Context,
- message.to, parsedEmailContext, message_id
- );
- } catch (error) {
- console.error("send webhook error", error);
- }
-
- // trigger another worker
- try {
- const parsedEmail = (await commonParseMail(parsedEmailContext));
- const parsedText = parsedEmail?.text ?? ""
- const rpcEmail: RPCEmailMessage = {
- from: message.from,
- to: message.to,
- rawEmail: rawEmail,
- headers: message.headers
- }
- await triggerAnotherWorker({ env: env } as Context, rpcEmail, parsedText);
- } catch (error) {
- console.error("trigger another worker error", error);
- }
-
- // auto reply email
- await auto_reply(message, env);
-}
-
-export { email }
diff --git a/cloudflare_temp_email/worker/src/i18n/en.ts b/cloudflare_temp_email/worker/src/i18n/en.ts
deleted file mode 100644
index 57e23f5..0000000
--- a/cloudflare_temp_email/worker/src/i18n/en.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { LocaleMessages } from "./type";
-
-const messages: LocaleMessages = {
- CustomAuthPasswordMsg: "You have enabled the private site password, please provide the password",
- UserTokenExpiredMsg: "Your token has expired, please login again",
- UserAcceesTokenExpiredMsg: "Your access token has expired, please refresh the page",
- UserRoleIsNotAdminMsg: "Your user role is not admin, no access to visit this page",
- NeedAdminPasswordMsg: "You need to provide the admin password to access this page",
-
- KVNotAvailableMsg: "KV is not available, please contact the administrator",
- DBNotAvailableMsg: "DB is not available, please contact the administrator",
- JWTSecretNotSetMsg: "JWT_SECRET is not set, please contact the administrator",
- WebhookNotEnabledMsg: "Webhook is not enabled, please contact the administrator",
- DomainsNotSetMsg: "Domains are not set, please contact the administrator",
-
- TurnstileCheckFailedMsg: "Human verification check failed",
- NewAddressDisabledMsg: "New address is disabled, please contact the administrator",
- NewAddressAnonymousDisabledMsg: "New address for anonymous user is disabled, please contact the administrator",
- FailedCreateAddressMsg: "Failed to create address",
- InvalidAddressMsg: "Invalid address",
- InvalidAddressCredentialMsg: "Invalid address credential",
- UserDeleteEmailDisabledMsg: "User delete address/email is disabled, please contact the administrator",
-
- UserNotFoundMsg: "User not found",
- UserAlreadyExistsMsg: "User already exists, please login",
- FailedToRegisterMsg: "Failed to register",
- UserRegistrationDisabledMsg: "User registration is disabled, please contact the administrator",
- UserMailDomainMustInMsg: "User mail domain must be in this list",
- InvalidVerifyCodeMsg: "Invalid verify code",
- InvalidEmailOrPasswordMsg: "Invalid email or password",
- VerifyMailSenderNotSetMsg: "Verify mail sender address is not set, please contact the administrator",
- CodeAlreadySentMsg: "Code already sent, please wait",
- InvalidUserDefaultRoleMsg: "Invalid user default role, please contact the administrator",
- FailedUpdateUserDefaultRoleMsg: "Failed to update user default role, please contact the administrator",
-
- Oauth2ClientIDNotFoundMsg: "Oauth2 client ID is not set, please contact the administrator",
- Oauth2CliendIDOrCodeMissingMsg: "Oauth2 client ID or code is missing",
- Oauth2FailedGetUserInfoMsg: "Failed to get user info from Oauth2 provider",
- Oauth2FailedGetAccessTokenMsg: "Failed to get access token from Oauth2 provider",
- Oauth2FailedGetUserEmailMsg: "Failed to get user email from Oauth2 provider",
-}
-
-export default messages;
diff --git a/cloudflare_temp_email/worker/src/i18n/index.ts b/cloudflare_temp_email/worker/src/i18n/index.ts
deleted file mode 100644
index b4b39f1..0000000
--- a/cloudflare_temp_email/worker/src/i18n/index.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { LocaleMessages } from "./type";
-import zh from "./zh";
-import en from "./en";
-import { Context } from "hono";
-
-export default {
- getMessages: (
- locale: string | null | undefined
- ): LocaleMessages => {
- // multi-language support
- if (locale === "en") return en;
- if (locale === "zh") return zh;
-
- // fallback language
- return en;
- },
- getMessagesbyContext: (
- c: Context
- ): LocaleMessages => {
- const locale = c.get("lang") || c.env.DEFAULT_LANG;
- // multi-language support
- if (locale === "en") return en;
- if (locale === "zh") return zh;
-
- // fallback language
- return en;
- }
-}
diff --git a/cloudflare_temp_email/worker/src/i18n/type.ts b/cloudflare_temp_email/worker/src/i18n/type.ts
deleted file mode 100644
index 2f31e59..0000000
--- a/cloudflare_temp_email/worker/src/i18n/type.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-export type LocaleMessages = {
- CustomAuthPasswordMsg: string
- UserTokenExpiredMsg: string
- UserAcceesTokenExpiredMsg: string
- UserRoleIsNotAdminMsg: string
- NeedAdminPasswordMsg: string
-
- KVNotAvailableMsg: string
- DBNotAvailableMsg: string
- JWTSecretNotSetMsg: string
- WebhookNotEnabledMsg: string
- DomainsNotSetMsg: string
-
- TurnstileCheckFailedMsg: string
- NewAddressDisabledMsg: string
- NewAddressAnonymousDisabledMsg: string
- FailedCreateAddressMsg: string
- InvalidAddressMsg: string
- InvalidAddressCredentialMsg: string
- UserDeleteEmailDisabledMsg: string
-
- UserNotFoundMsg: string
- UserAlreadyExistsMsg: string
- FailedToRegisterMsg: string
- UserRegistrationDisabledMsg: string
- UserMailDomainMustInMsg: string
- InvalidVerifyCodeMsg: string
- InvalidEmailOrPasswordMsg: string
- VerifyMailSenderNotSetMsg: string
- CodeAlreadySentMsg: string
- InvalidUserDefaultRoleMsg: string
- FailedUpdateUserDefaultRoleMsg: string
-
- Oauth2ClientIDNotFoundMsg: string
- Oauth2CliendIDOrCodeMissingMsg: string
- Oauth2FailedGetUserInfoMsg: string
- Oauth2FailedGetAccessTokenMsg: string
- Oauth2FailedGetUserEmailMsg: string
-}
diff --git a/cloudflare_temp_email/worker/src/i18n/zh.ts b/cloudflare_temp_email/worker/src/i18n/zh.ts
deleted file mode 100644
index b55d0cd..0000000
--- a/cloudflare_temp_email/worker/src/i18n/zh.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { LocaleMessages } from "./type";
-
-const messages: LocaleMessages = {
- CustomAuthPasswordMsg: "你已启用私有站点密码,请提供密码",
- UserTokenExpiredMsg: "您的令牌已过期, 请重新登录",
- UserAcceesTokenExpiredMsg: "您的访问令牌已过期, 请刷新页面",
- UserRoleIsNotAdminMsg: "您的用户角色不是管理员, 无权访问",
- NeedAdminPasswordMsg: "您需要提供管理员密码才能访问此页面",
-
- KVNotAvailableMsg: "KV 不可用, 请联系管理员",
- DBNotAvailableMsg: "DB 不可用, 请联系管理员",
- JWTSecretNotSetMsg: "JWT_SECRET 未设置, 请联系管理员",
- WebhookNotEnabledMsg: "Webhook 未启用, 请联系管理员",
- DomainsNotSetMsg: "域名列表未设置, 请联系管理员",
-
- TurnstileCheckFailedMsg: "人机验证检查失败",
- NewAddressDisabledMsg: "新建邮箱地址已禁用, 请联系管理员",
- NewAddressAnonymousDisabledMsg: "匿名用户新建邮箱地址已禁用, 请联系管理员",
- FailedCreateAddressMsg: "创建邮箱地址失败",
- InvalidAddressMsg: "无效的邮箱地址",
- InvalidAddressCredentialMsg: "无效的邮箱地址凭据",
- UserDeleteEmailDisabledMsg: "用户删除邮箱/邮件已禁用, 请联系管理员",
-
- UserNotFoundMsg: "用户不存在",
- UserAlreadyExistsMsg: "用户已存在, 请登录",
- FailedToRegisterMsg: "注册失败",
- UserRegistrationDisabledMsg: "用户注册已禁用, 请联系管理员",
- UserMailDomainMustInMsg: "用户邮箱域必须在此列表中",
- InvalidVerifyCodeMsg: "无效的验证码",
- InvalidEmailOrPasswordMsg: "无效的邮箱或密码",
- VerifyMailSenderNotSetMsg: "验证邮件发送邮箱未设置, 请联系管理员",
- CodeAlreadySentMsg: "验证码已发送, 请稍等",
- InvalidUserDefaultRoleMsg: "无效的用户默认角色, 请联系管理员",
- FailedUpdateUserDefaultRoleMsg: "更新用户默认角色失败, 请联系管理员",
-
- Oauth2ClientIDNotFoundMsg: "Oauth2 客户端 ID 未设置, 请联系管理员",
- Oauth2CliendIDOrCodeMissingMsg: "Oauth2 客户端 ID 或 code 缺失",
- Oauth2FailedGetUserInfoMsg: "从 Oauth2 提供商获取用户信息失败",
- Oauth2FailedGetAccessTokenMsg: "从 Oauth2 提供商获取访问令牌失败",
- Oauth2FailedGetUserEmailMsg: "从 Oauth2 提供商获取用户邮箱失败",
-}
-
-export default messages;
diff --git a/cloudflare_temp_email/worker/src/mails_api/auto_reply.ts b/cloudflare_temp_email/worker/src/mails_api/auto_reply.ts
deleted file mode 100644
index 0b461b5..0000000
--- a/cloudflare_temp_email/worker/src/mails_api/auto_reply.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { Context } from "hono";
-import { getBooleanValue } from "../utils";
-
-
-export default {
- getAutoReply: async (c: Context) => {
- if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) {
- return c.text("Auto reply is disabled", 403)
- }
- const { address } = c.get("jwtPayload")
- const results = await c.env.DB.prepare(
- `SELECT * FROM auto_reply_mails where address = ? `
- ).bind(address).first();
- if (!results) {
- return c.json({});
- }
- return c.json({
- subject: results.subject,
- message: results.message,
- enabled: results.enabled == 1,
- source_prefix: results.source_prefix,
- name: results.name,
- })
- },
- saveAutoReply: async (c: Context) => {
- if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) {
- return c.text("Auto reply is disabled", 403)
- }
- const { address } = c.get("jwtPayload");
- const { auto_reply } = await c.req.json();
- const { name, subject, source_prefix, message, enabled } = auto_reply;
- if ((!subject || !message) && enabled) {
- return c.text("Invalid subject or message", 400)
- }
- else if (subject.length > 255 || message.length > 255) {
- return c.text("Subject or message too long", 400)
- }
- const { success } = await c.env.DB.prepare(
- `INSERT OR REPLACE INTO auto_reply_mails`
- + ` (name, address, source_prefix, subject, message, enabled)`
- + ` VALUES (?, ?, ?, ?, ?, ?)`
- ).bind(
- name || '', address, source_prefix || '',
- subject || '', message || '', enabled ? 1 : 0
- ).run();
- if (!success) {
- return c.text("Failed to auto_reply settings", 500)
- }
- return c.json({
- success: success
- })
- }
-}
diff --git a/cloudflare_temp_email/worker/src/mails_api/index.ts b/cloudflare_temp_email/worker/src/mails_api/index.ts
deleted file mode 100644
index 3a02998..0000000
--- a/cloudflare_temp_email/worker/src/mails_api/index.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-import { Context, Hono } from 'hono'
-
-import i18n from '../i18n';
-import { getBooleanValue, getJsonSetting, checkCfTurnstile, getStringValue, getSplitStringListValue } from '../utils';
-import { newAddress, handleListQuery, deleteAddressWithData, getAddressPrefix, getAllowDomains, updateAddressUpdatedAt, generateRandomName } from '../common'
-import { CONSTANTS } from '../constants'
-import auto_reply from './auto_reply'
-import webhook_settings from './webhook_settings';
-import s3_attachment from './s3_attachment';
-
-export const api = new Hono()
-
-api.get('/api/auto_reply', auto_reply.getAutoReply)
-api.post('/api/auto_reply', auto_reply.saveAutoReply)
-api.get('/api/webhook/settings', webhook_settings.getWebhookSettings)
-api.post('/api/webhook/settings', webhook_settings.saveWebhookSettings)
-api.post('/api/webhook/test', webhook_settings.testWebhookSettings)
-api.get('/api/attachment/list', s3_attachment.list)
-api.post('/api/attachment/delete', s3_attachment.deleteKey)
-api.post('/api/attachment/put_url', s3_attachment.getSignedPutUrl)
-api.post('/api/attachment/get_url', s3_attachment.getSignedGetUrl)
-
-api.get('/api/mails', async (c) => {
- const { address } = c.get("jwtPayload")
- if (!address) {
- return c.json({ "error": "No address" }, 400)
- }
- const { limit, offset } = c.req.query();
- if (Number.parseInt(offset) <= 0) await updateAddressUpdatedAt(c, address);
- return await handleListQuery(c,
- `SELECT * FROM raw_mails where address = ?`,
- `SELECT count(*) as count FROM raw_mails where address = ?`,
- [address], limit, offset
- );
-})
-
-api.get('/api/mail/:mail_id', async (c) => {
- const { address } = c.get("jwtPayload")
- const { mail_id } = c.req.param();
- const result = await c.env.DB.prepare(
- `SELECT * FROM raw_mails where id = ? and address = ?`
- ).bind(mail_id, address).first();
- return c.json(result);
-})
-
-api.delete('/api/mails/:id', async (c) => {
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
- return c.text(msgs.UserDeleteEmailDisabledMsg, 403)
- }
- const { address } = c.get("jwtPayload")
- const { id } = c.req.param();
- // TODO: add toLowerCase() to handle old data
- const { success } = await c.env.DB.prepare(
- `DELETE FROM raw_mails WHERE address = ? and id = ? `
- ).bind(address.toLowerCase(), id).run();
- return c.json({
- success: success
- })
-})
-
-api.get('/api/settings', async (c) => {
- const { address, address_id } = c.get("jwtPayload")
- const user_role = c.get("userRolePayload")
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- if (address_id && address_id > 0) {
- try {
- const db_address_id = await c.env.DB.prepare(
- `SELECT id FROM address where id = ? `
- ).bind(address_id).first("id");
- if (!db_address_id) {
- return c.text(msgs.InvalidAddressMsg, 400)
- }
- } catch (error) {
- return c.text(msgs.InvalidAddressMsg, 400)
- }
- }
- // check address id
- try {
- if (!address_id) {
- const db_address_id = await c.env.DB.prepare(
- `SELECT id FROM address where name = ? `
- ).bind(address).first("id");
- if (!db_address_id) {
- return c.text(msgs.InvalidAddressMsg, 400)
- }
- }
- } catch (error) {
- return c.text(msgs.InvalidAddressMsg, 400)
- }
-
- await updateAddressUpdatedAt(c, address);
-
- const no_limit_roles = getSplitStringListValue(c.env.NO_LIMIT_SEND_ROLE);
- const is_no_limit_send_balance = user_role && no_limit_roles.includes(user_role);
- const balance = is_no_limit_send_balance ? 99999 : await c.env.DB.prepare(
- `SELECT balance FROM address_sender where address = ? and enabled = 1`
- ).bind(address).first("balance");
- return c.json({
- address: address,
- send_balance: balance || 0,
- });
-})
-
-api.post('/api/new_address', async (c) => {
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- if (getBooleanValue(c.env.DISABLE_ANONYMOUS_USER_CREATE_EMAIL)
- && !c.get("userPayload")
- ) {
- return c.text(msgs.NewAddressAnonymousDisabledMsg, 403)
- }
- if (!getBooleanValue(c.env.ENABLE_USER_CREATE_EMAIL)) {
- return c.text(msgs.NewAddressDisabledMsg, 403)
- }
- // eslint-disable-next-line prefer-const
- let { name, domain, cf_token } = await c.req.json();
- // check cf turnstile
- try {
- await checkCfTurnstile(c, cf_token);
- } catch (error) {
- return c.text(msgs.TurnstileCheckFailedMsg, 500)
- }
- // Check if custom email names are disabled from environment variable
- const disableCustomAddressName = getBooleanValue(c.env.DISABLE_CUSTOM_ADDRESS_NAME);
-
- // if no name or custom names are disabled, generate random name
- if (!name || disableCustomAddressName) {
- // Generate random name with context-based length configuration
- name = generateRandomName(c);
- }
- // check name block list
- try {
- const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
- const blockList = (value || []) as string[];
- if (blockList.some((item) => name.includes(item))) {
- return c.text(`Name[${name}]is blocked`, 400)
- }
- } catch (error) {
- console.error(error);
- }
- try {
- const addressPrefix = await getAddressPrefix(c);
- const res = await newAddress(c, {
- name, domain,
- enablePrefix: true,
- checkLengthByConfig: true,
- addressPrefix
- });
- return c.json(res);
- } catch (e) {
- return c.text(`${msgs.FailedCreateAddressMsg}: ${(e as Error).message}`, 400)
- }
-})
-
-api.delete('/api/delete_address', async (c) => {
- const { address, address_id } = c.get("jwtPayload")
- const success = await deleteAddressWithData(c, address, address_id);
- return c.json({
- success: success
- })
-})
-
-api.delete('/api/clear_inbox', async (c) => {
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
- return c.text(msgs.UserDeleteEmailDisabledMsg, 403)
- }
- const { address } = c.get("jwtPayload")
- const { success } = await c.env.DB.prepare(
- `DELETE FROM raw_mails WHERE address = ?`
- ).bind(address).run();
- if (!success) {
- return c.text("Failed to clear inbox", 500)
- }
- return c.json({
- success: success
- })
-})
-
-api.delete('/api/clear_sent_items', async (c) => {
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
- return c.text(msgs.UserDeleteEmailDisabledMsg, 403)
- }
- const { address } = c.get("jwtPayload")
- const { success } = await c.env.DB.prepare(
- `DELETE FROM sendbox WHERE address = ?`
- ).bind(address).run();
- if (!success) {
- return c.text("Failed to clear sent items", 500)
- }
- return c.json({
- success: success
- })
-})
diff --git a/cloudflare_temp_email/worker/src/mails_api/s3_attachment.ts b/cloudflare_temp_email/worker/src/mails_api/s3_attachment.ts
deleted file mode 100644
index 2c16c42..0000000
--- a/cloudflare_temp_email/worker/src/mails_api/s3_attachment.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { Context } from "hono";
-import {
- S3Client,
- ListObjectsV2Command,
- GetObjectCommand,
- PutObjectCommand,
- DeleteObjectCommand
-} from "@aws-sdk/client-s3";
-import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
-
-export const isS3Enabled = (c: Context) => {
- return !(!c.env.S3_ENDPOINT ||
- !c.env.S3_ACCESS_KEY_ID ||
- !c.env.S3_SECRET_ACCESS_KEY ||
- !c.env.S3_BUCKET);
-}
-
-const getS3Client = (c: Context) => {
- if (
- !c.env.S3_ENDPOINT ||
- !c.env.S3_ACCESS_KEY_ID ||
- !c.env.S3_SECRET_ACCESS_KEY ||
- !c.env.S3_BUCKET
- ) {
- throw new Error("S3 config is not set");
- }
- return new S3Client({
- region: "auto",
- endpoint: c.env.S3_ENDPOINT,
- credentials: {
- accessKeyId: c.env.S3_ACCESS_KEY_ID,
- secretAccessKey: c.env.S3_SECRET_ACCESS_KEY,
- },
- });
-}
-
-export default {
- getSignedGetUrl: async (c: Context) => {
- const { address } = c.get("jwtPayload")
- const { key } = await c.req.json()
- const client = getS3Client(c);
- const url = await getSignedUrl(
- client,
- new GetObjectCommand({
- Bucket: c.env.S3_BUCKET,
- Key: `${address}/${key}`
- }),
- { expiresIn: c.env.S3_URL_EXPIRES || 360 }
- );
- return c.json({ url });
- },
- getSignedPutUrl: async (c: Context) => {
- const { address } = c.get("jwtPayload")
- const { key } = await c.req.json()
- const client = getS3Client(c);
- const url = await getSignedUrl(
- client,
- new PutObjectCommand({
- Bucket: c.env.S3_BUCKET,
- Key: `${address}/${key}`
- }),
- { expiresIn: c.env.S3_URL_EXPIRES || 360 }
- );
- return c.json({ url });
- },
- list: async (c: Context) => {
- const { address } = c.get("jwtPayload")
- const client = getS3Client(c);
- const data = await client.send(
- new ListObjectsV2Command({
- Bucket: c.env.S3_BUCKET,
- Prefix: `${address}/`
- })
- );
- return c.json(
- {
- results: data?.Contents
- ?.map((v) => v.Key?.replace(`${address}/`, ""))
- ?.filter(k => k)
- ?.map((k) => ({ key: k }))
- }
- );
- },
- deleteKey: async (c: Context) => {
- const { address } = c.get("jwtPayload")
- const { key } = await c.req.json()
- const client = getS3Client(c);
- await client.send(
- new DeleteObjectCommand({
- Bucket: c.env.S3_BUCKET,
- Key: `${address}/${key}`
- })
- );
- return c.json({ success: true });
- }
-}
diff --git a/cloudflare_temp_email/worker/src/mails_api/send_mail_api.ts b/cloudflare_temp_email/worker/src/mails_api/send_mail_api.ts
deleted file mode 100644
index 18d5156..0000000
--- a/cloudflare_temp_email/worker/src/mails_api/send_mail_api.ts
+++ /dev/null
@@ -1,305 +0,0 @@
-import { Context, Hono } from 'hono'
-import { Jwt } from 'hono/utils/jwt'
-import { createMimeMessage } from 'mimetext';
-import { Resend } from 'resend';
-import { WorkerMailer, WorkerMailerOptions } from 'worker-mailer';
-
-import i18n from '../i18n';
-import { CONSTANTS } from '../constants'
-import { getJsonSetting, getDomains, getIntValue, getBooleanValue, getStringValue, getJsonObjectValue, getSplitStringListValue } from '../utils';
-import { GeoData } from '../models'
-import { handleListQuery } from '../common'
-
-
-export const api = new Hono()
-
-api.post('/api/requset_send_mail_access', async (c) => {
- const { address } = c.get("jwtPayload")
- if (!address) {
- return c.text("No address", 400)
- }
- try {
- const default_balance = getIntValue(c.env.DEFAULT_SEND_BALANCE, 0);
- const { success } = await c.env.DB.prepare(
- `INSERT INTO address_sender (address, balance, enabled) VALUES (?, ?, ?)`
- ).bind(
- address, default_balance, default_balance > 0 ? 1 : 0
- ).run();
- if (!success) {
- return c.text("Failed to request send mail access", 500)
- }
- } catch (e) {
- const message = (e as Error).message;
- if (message && message.includes("UNIQUE")) {
- return c.text("Already requested", 400)
- }
- return c.text("Failed to request send mail access", 500)
- }
- return c.json({ status: "ok" })
-})
-
-export const sendMailToVerifyAddress = async (
- c: Context, address: string,
- reqJson: {
- from_name: string, to_mail: string, to_name: string,
- subject: string, content: string, is_html: boolean
- }
-): Promise => {
- const {
- from_name, to_mail, to_name,
- subject, content, is_html
- } = reqJson;
- const msg = createMimeMessage();
- msg.setSender(from_name ? { name: from_name, addr: address } : address);
- msg.setRecipient(to_name ? { name: to_name, addr: to_mail } : to_mail);
- msg.setSubject(subject);
- msg.addMessage({
- contentType: is_html ? 'text/html' : 'text/plain',
- data: content
- });
- const { EmailMessage } = await import('cloudflare:email');
- const message = new EmailMessage(address, to_mail, msg.asRaw());
- await c.env.SEND_MAIL.send(message);
-}
-
-const sendMailByResend = async (
- c: Context, address: string,
- reqJson: {
- from_name: string, to_mail: string, to_name: string,
- subject: string, content: string, is_html: boolean
- }
-): Promise => {
- const mailDomain = address.split("@")[1];
- const token = c.env[
- `RESEND_TOKEN_${mailDomain.replace(/\./g, "_").toUpperCase()}`
- ] || c.env.RESEND_TOKEN;
- const resend = new Resend(token);
- const { data, error } = await resend.emails.send({
- from: reqJson.from_name ? `${reqJson.from_name} <${address}>` : address,
- to: reqJson.to_name ? `${reqJson.to_name} <${reqJson.to_mail}>` : reqJson.to_mail,
- subject: reqJson.subject,
- ...(reqJson.is_html ? {
- html: reqJson.content,
- } : {
- text: reqJson.content,
- })
- });
- if (error) {
- throw new Error(`Resend error: ${error.name} ${error.message}`);
- }
- console.log(`Resend success: ${JSON.stringify(data)}`);
-}
-
-const sendMailBySmtp = async (
- c: Context, address: string,
- reqJson: {
- from_name: string, to_mail: string, to_name: string,
- subject: string, content: string, is_html: boolean
- },
- smtpOptions: WorkerMailerOptions
-): Promise => {
- await WorkerMailer.send(
- smtpOptions,
- {
- from: {
- name: reqJson.from_name,
- email: address
- },
- to: {
- name: reqJson.to_name,
- email: reqJson.to_mail
- },
- subject: reqJson.subject,
- text: reqJson.is_html ? undefined : reqJson.content,
- html: reqJson.is_html ? reqJson.content : undefined
- }
- )
-}
-
-export const sendMail = async (
- c: Context, address: string,
- reqJson: {
- from_name: string, to_mail: string, to_name: string,
- subject: string, content: string, is_html: boolean
- },
- options?: {
- isAdmin?: boolean
- }
-): Promise => {
- if (!address) {
- throw new Error("No address")
- }
- // check domain
- const mailDomain = address.split("@")[1];
- const domains = getDomains(c);
- if (!domains.includes(mailDomain)) {
- throw new Error("Invalid domain")
- }
- const user_role = c.get("userRolePayload");
- const no_limit_roles = getSplitStringListValue(c.env.NO_LIMIT_SEND_ROLE);
- const is_no_limit_send_balance = user_role && no_limit_roles.includes(user_role);
- // no need find noLimitSendAddressList if is_no_limit_send_balance
- const noLimitSendAddressList = is_no_limit_send_balance ?
- [] : await getJsonSetting(c, CONSTANTS.NO_LIMIT_SEND_ADDRESS_LIST_KEY) || [];
- const isNoLimitSendAddress = noLimitSendAddressList?.includes(address);
- const needCheckBalance = !is_no_limit_send_balance && !options?.isAdmin && !isNoLimitSendAddress;
- if (needCheckBalance) {
- // check permission
- const balance = await c.env.DB.prepare(
- `SELECT balance FROM address_sender
- where address = ? and enabled = 1`
- ).bind(address).first("balance");
- if (!balance || balance <= 0) {
- throw new Error("No balance")
- }
- }
- const {
- from_name, to_mail, to_name,
- subject, content, is_html
- } = reqJson;
- if (!to_mail) {
- throw new Error("Invalid to mail")
- }
- // check SEND_BLOCK_LIST_KEY
- const sendBlockList = await getJsonSetting(c, CONSTANTS.SEND_BLOCK_LIST_KEY) as string[];
- if (sendBlockList && sendBlockList.some((item) => to_mail.includes(item))) {
- throw new Error("to_mail address is blocked")
- }
- if (!subject) {
- throw new Error("Subject is empty")
- }
- if (!content) {
- throw new Error("Content is empty")
- }
-
- // send to verified address list, do not update balance
- const resendEnabled = c.env.RESEND_TOKEN || c.env[
- `RESEND_TOKEN_${mailDomain.replace(/\./g, "_").toUpperCase()}`
- ];
- // send by smtp
- const smtpConfigMap = getJsonObjectValue>(c.env.SMTP_CONFIG);
- const smtpConfig = smtpConfigMap ? smtpConfigMap[mailDomain] : null;
- // send by verified address list
- let sendByVerifiedAddressList = false;
- if (c.env.SEND_MAIL) {
- const verifiedAddressList = await getJsonSetting(c, CONSTANTS.VERIFIED_ADDRESS_LIST_KEY) || [];
- if (verifiedAddressList.includes(to_mail)) {
- await sendMailToVerifyAddress(c, address, reqJson);
- sendByVerifiedAddressList = true;
- }
- }
-
- // send mail workflow
- if (sendByVerifiedAddressList) {
- // do not update balance
- }
- // send by resend
- else if (resendEnabled) {
- await sendMailByResend(c, address, reqJson);
- }
- else if (smtpConfig) {
- await sendMailBySmtp(c, address, reqJson, smtpConfig);
- }
- else {
- if (c.env.SEND_MAIL) {
- throw new Error(`Please enable resend or smtp for domain ${mailDomain}. Or add ${to_mail} to verified address list`);
- }
- throw new Error(`Please enable resend or smtp for domain ${mailDomain}`);
- }
-
- // update balance
- if (!sendByVerifiedAddressList && needCheckBalance) {
- try {
- const { success } = await c.env.DB.prepare(
- `UPDATE address_sender SET balance = balance - 1 where address = ?`
- ).bind(address).run();
- if (!success) {
- console.warn(`Failed to update balance for ${address}`);
- }
- } catch (e) {
- console.warn(`Failed to update balance for ${address}`);
- }
- }
- // save to sendbox
- try {
- const reqIp = c.req.raw.headers.get("cf-connecting-ip")
- const geoData = new GeoData(reqIp, c.req.raw.cf as any);
- const body = {
- version: "v2",
- ...reqJson,
- geoData: geoData,
- };
- const { success: success2 } = await c.env.DB.prepare(
- `INSERT INTO sendbox (address, raw) VALUES (?, ?)`
- ).bind(address, JSON.stringify(body)).run();
- if (!success2) {
- console.warn(`Failed to save to sendbox for ${address}`);
- }
- } catch (e) {
- console.warn(`Failed to save to sendbox for ${address}`);
- }
-}
-
-api.post('/api/send_mail', async (c) => {
- const { address } = c.get("jwtPayload")
- const reqJson = await c.req.json();
- try {
- await sendMail(c, address, reqJson);
- } catch (e) {
- console.error("Failed to send mail", e);
- return c.text(`Failed to send mail ${(e as Error).message}`, 400)
- }
- return c.json({ status: "ok" })
-})
-
-api.post('/external/api/send_mail', async (c) => {
- const { token } = await c.req.json();
- try {
- const { address } = await Jwt.verify(token, c.env.JWT_SECRET, "HS256");
- if (!address) {
- return c.text("No address", 400)
- }
- const reqJson = await c.req.json();
- await sendMail(c, address as string, reqJson);
- return c.json({ status: "ok" })
- } catch (e) {
- console.error("Failed to send mail", e);
- return c.text(`Failed to send mail ${(e as Error).message}`, 400)
- }
-})
-
-export const getSendbox = async (
- c: Context,
- address: string, limit: string, offset: string
-): Promise => {
- if (!address) {
- return c.json({ "error": "No address" }, 400)
- }
- return await handleListQuery(c,
- `SELECT * FROM sendbox where address = ? `,
- `SELECT count(*) as count FROM sendbox where address = ? `,
- [address], limit, offset
- );
-}
-
-api.get('/api/sendbox', async (c) => {
- const { address } = c.get("jwtPayload")
- const { limit, offset } = c.req.query();
- return getSendbox(c, address, limit, offset);
-})
-
-api.delete('/api/sendbox/:id', async (c) => {
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
- return c.text(msgs.UserDeleteEmailDisabledMsg, 403)
- }
- const { address } = c.get("jwtPayload")
- const { id } = c.req.param();
- const { success } = await c.env.DB.prepare(
- `DELETE FROM sendbox WHERE address = ? and id = ? `
- ).bind(address, id).run();
- return c.json({
- success: success
- })
-})
diff --git a/cloudflare_temp_email/worker/src/mails_api/webhook_settings.ts b/cloudflare_temp_email/worker/src/mails_api/webhook_settings.ts
deleted file mode 100644
index 4e016d7..0000000
--- a/cloudflare_temp_email/worker/src/mails_api/webhook_settings.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Context } from "hono";
-import { CONSTANTS } from "../constants";
-import { AdminWebhookSettings, WebhookSettings } from "../models";
-import { commonParseMail, sendWebhook } from "../common";
-
-
-async function getWebhookSettings(c: Context): Promise {
- const { address } = c.get("jwtPayload")
- const adminSettings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json");
- if (adminSettings?.enableAllowList && !adminSettings?.allowList.includes(address)) {
- return c.text("Webhook settings is not allowed for this user", 403);
- }
- const settings = await c.env.KV.get(
- `${CONSTANTS.WEBHOOK_KV_USER_SETTINGS_KEY}:${address}`, "json"
- ) || new WebhookSettings();
- return c.json(settings);
-}
-
-
-async function saveWebhookSettings(c: Context): Promise {
- const { address } = c.get("jwtPayload")
- const adminSettings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json");
- if (adminSettings?.enableAllowList && !adminSettings?.allowList.includes(address)) {
- return c.text("Webhook settings is not allowed for this user", 403);
- }
- const settings = await c.req.json();
- await c.env.KV.put(
- `${CONSTANTS.WEBHOOK_KV_USER_SETTINGS_KEY}:${address}`,
- JSON.stringify(settings));
- return c.json({ success: true })
-}
-
-async function testWebhookSettings(c: Context): Promise {
- const settings = await c.req.json();
- const { address } = c.get("jwtPayload");
- // random raw email
- const { id: mailId, raw } = await c.env.DB.prepare(
- `SELECT id, raw FROM raw_mails WHERE address = ? ORDER BY RANDOM() LIMIT 1`
- ).bind(address).first<{ id: string, raw: string }>() || {};
- const parsedEmailContext: ParsedEmailContext = { rawEmail: raw || "" };
- const parsedEmail = await commonParseMail(parsedEmailContext);
- const res = await sendWebhook(settings, {
- id: mailId || "0",
- url: c.env.FRONTEND_URL ? `${c.env.FRONTEND_URL}?mail_id=${mailId}` : "",
- from: parsedEmail?.sender || "test@test.com",
- to: address,
- subject: parsedEmail?.subject || "test subject",
- raw: raw || "test raw email",
- parsedText: parsedEmail?.text || "test parsed text",
- parsedHtml: parsedEmail?.html || "test parsed html"
- });
- if (!res.success) {
- return c.text(res.message || "send webhook error", 400);
- }
- return c.json({ success: true });
-}
-
-export default {
- getWebhookSettings,
- saveWebhookSettings,
- testWebhookSettings,
-}
diff --git a/cloudflare_temp_email/worker/src/models/index.ts b/cloudflare_temp_email/worker/src/models/index.ts
deleted file mode 100644
index 4b3ea62..0000000
--- a/cloudflare_temp_email/worker/src/models/index.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-import type {
- AuthenticatorTransportFuture,
- CredentialDeviceType,
- Base64URLString,
-} from '@simplewebauthn/types';
-
-export type Passkey = {
- id: Base64URLString;
- publicKey: string;
- counter: number;
- deviceType: CredentialDeviceType;
- backedUp: boolean;
- transports?: AuthenticatorTransportFuture[];
-};
-
-export class AdminWebhookSettings {
- enableAllowList: boolean;
- allowList: string[];
-
- constructor(enableAllowList: boolean, allowList: string[]) {
- this.enableAllowList = enableAllowList;
- this.allowList = allowList;
- }
-}
-
-export type WebhookMail = {
- id: string;
- url?: string;
- from: string;
- to: string;
- subject: string;
- raw: string;
- parsedText: string;
- parsedHtml: string;
-}
-
-export type CleanupSettings = {
-
- enableMailsAutoCleanup: boolean | undefined;
- cleanMailsDays: number;
- enableUnknowMailsAutoCleanup: boolean | undefined;
- cleanUnknowMailsDays: number;
- enableSendBoxAutoCleanup: boolean | undefined;
- cleanSendBoxDays: number;
- enableAddressAutoCleanup: boolean | undefined;
- cleanAddressDays: number;
- enableInactiveAddressAutoCleanup: boolean | undefined;
- cleanInactiveAddressDays: number;
-}
-
-export class GeoData {
-
- ip: string;
- country: string | undefined;
- city: string | undefined;
- timezone: string | undefined;
- postalCode: string | undefined;
- region: string | undefined;
- latitude: number | undefined;
- longitude: number | undefined;
- regionCode: string | undefined;
- asOrganization: string | undefined;
-
- constructor(ip: string | null, data: GeoData | undefined | null) {
- const {
- country, city, timezone, postalCode, region,
- latitude, longitude, regionCode, asOrganization
- } = data || {};
- this.ip = ip || "unknown";
- this.country = country;
- this.city = city;
- this.timezone = timezone;
- this.postalCode = postalCode;
- this.region = region;
- this.latitude = latitude;
- this.longitude = longitude;
- this.regionCode = regionCode;
- this.asOrganization = asOrganization;
- }
-}
-
-export class UserSettings {
-
- enable: boolean | undefined;
- enableMailVerify: boolean | undefined;
- verifyMailSender: string | undefined;
- enableMailAllowList: boolean | undefined;
- mailAllowList: string[] | undefined;
- maxAddressCount: number;
-
- constructor(data: UserSettings | undefined | null) {
- const {
- enable, enableMailVerify, verifyMailSender,
- enableMailAllowList, mailAllowList, maxAddressCount
- } = data || {};
- this.enable = enable;
- this.enableMailVerify = enableMailVerify;
- this.verifyMailSender = verifyMailSender;
- this.enableMailAllowList = enableMailAllowList;
- this.mailAllowList = mailAllowList;
- this.maxAddressCount = maxAddressCount || 5;
- }
-}
-
-export class UserInfo {
-
- geoData: GeoData;
- userEmail: string;
-
- constructor(geoData: GeoData, userEmail: string) {
- this.geoData = geoData;
- this.userEmail = userEmail;
- }
-}
-
-export class WebhookSettings {
- enabled: boolean = false
- url: string = ''
- method: string = 'POST'
- headers: string = JSON.stringify({
- "Content-Type": "application/json"
- }, null, 2)
- body: string = JSON.stringify({
- "id": "${id}",
- "url": "${url}",
- "from": "${from}",
- "to": "${to}",
- "subject": "${subject}",
- "raw": "${raw}",
- "parsedText": "${parsedText}",
- "parsedHtml": "${parsedHtml}",
- }, null, 2)
-}
-
-export type UserOauth2Settings = {
- name: string;
- clientID: string;
- clientSecret: string;
- authorizationURL: string;
- accessTokenURL: string;
- accessTokenFormat: string;
- userInfoURL: string;
- redirectURL: string;
- logoutURL?: string;
- userEmailKey: string;
- scope: string;
- enableMailAllowList?: boolean | undefined;
- mailAllowList?: string[] | undefined;
-}
-
-export type EmailRuleSettings = {
- blockReceiveUnknowAddressEmail: boolean;
- emailForwardingList: SubdomainForwardAddressList[]
-}
diff --git a/cloudflare_temp_email/worker/src/scheduled.ts b/cloudflare_temp_email/worker/src/scheduled.ts
deleted file mode 100644
index 971c86f..0000000
--- a/cloudflare_temp_email/worker/src/scheduled.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { Context } from 'hono';
-import { cleanup } from './common'
-import { CONSTANTS } from './constants'
-import { getJsonSetting } from './utils';
-import { CleanupSettings } from './models';
-
-export async function scheduled(event: ScheduledEvent, env: Bindings, ctx: any) {
- console.log("Scheduled event: ", event);
- const autoCleanupSetting = await getJsonSetting(
- { env: env, } as Context,
- CONSTANTS.AUTO_CLEANUP_KEY
- );
- if (!autoCleanupSetting) {
- console.log("No auto cleanup settings found, skipping cleanup.");
- return;
- }
- console.log("autoCleanupSetting:", JSON.stringify(autoCleanupSetting));
- if (autoCleanupSetting.enableMailsAutoCleanup) {
- await cleanup(
- { env: env, } as Context,
- "mails",
- autoCleanupSetting.cleanMailsDays
- );
- }
- if (autoCleanupSetting.enableUnknowMailsAutoCleanup) {
- await cleanup(
- { env: env, } as Context,
- "mails_unknow",
- autoCleanupSetting.cleanUnknowMailsDays
- );
- }
- if (autoCleanupSetting.enableSendBoxAutoCleanup) {
- await cleanup(
- { env: env, } as Context,
- "sendbox",
- autoCleanupSetting.cleanSendBoxDays
- );
- }
- if (autoCleanupSetting.enableInactiveAddressAutoCleanup) {
- await cleanup(
- { env: env, } as Context,
- "inactiveAddress",
- autoCleanupSetting.cleanInactiveAddressDays
- );
- }
- if (autoCleanupSetting.enableAddressAutoCleanup) {
- await cleanup(
- { env: env, } as Context,
- "addressCreated",
- autoCleanupSetting.cleanAddressDays
- );
- }
-}
diff --git a/cloudflare_temp_email/worker/src/telegram_api/common.ts b/cloudflare_temp_email/worker/src/telegram_api/common.ts
deleted file mode 100644
index 3a075dc..0000000
--- a/cloudflare_temp_email/worker/src/telegram_api/common.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import { Context } from "hono";
-import { Jwt } from "hono/utils/jwt";
-import { CONSTANTS } from "../constants";
-import { getBooleanValue, getIntValue, getJsonSetting } from "../utils";
-import { deleteAddressWithData, newAddress, generateRandomName } from "../common";
-
-export const tgUserNewAddress = async (
- c: Context, userId: string, address: string
-): Promise<{ address: string, jwt: string }> => {
- if (c.env.RATE_LIMITER) {
- const { success } = await c.env.RATE_LIMITER.limit(
- { key: `${CONSTANTS.TG_KV_PREFIX}:${userId}` }
- )
- if (!success) {
- throw Error("Rate limit exceeded")
- }
- }
- // Check if custom address names are disabled
- const disableCustomAddressName = getBooleanValue(c.env.DISABLE_CUSTOM_ADDRESS_NAME);
-
- // Parse address parameter - handle empty or whitespace-only address
- const trimmedAddress = address ? address.trim() : "";
- const [name, domain] = trimmedAddress.includes("@") ? trimmedAddress.split("@") : [trimmedAddress, null];
- const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
- if (jwtList.length >= getIntValue(c.env.TG_MAX_ADDRESS, 5)) {
- throw Error("绑定地址数量已达上限");
- }
- // Generate name if disabled or not provided
- const finalName = (!name || disableCustomAddressName) ? generateRandomName(c) : name;
-
- // check name block list
- const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
- const blockList = (value || []) as string[];
- if (blockList.some((item) => finalName.includes(item))) {
- throw Error(`Name[${finalName}]is blocked`);
- }
-
- const res = await newAddress(c, {
- name: finalName,
- domain,
- enablePrefix: true
- });
- // for mail push to telegram
- await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify([...jwtList, res.jwt]));
- await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${res.address}`, userId.toString());
- return res;
-}
-
-export const jwtListToAddressData = async (
- c: Context, jwtList: string[]
-): Promise<{
- addressList: string[], addressIdMap: Record,
- invalidJwtList: string[]
-}> => {
- const addressList = [] as string[];
- const addressIdMap = {} as Record;
- const invalidJwtList = [] as string[];
- for (const jwt of jwtList) {
- try {
- const { address, address_id } = await Jwt.verify(jwt, c.env.JWT_SECRET, "HS256");
- const name = await c.env.DB.prepare(
- `SELECT name FROM address WHERE id = ? `
- ).bind(address_id).first("name");
- if (!name) {
- addressList.push("无效地址");
- invalidJwtList.push(jwt);
- continue;
- }
- addressList.push(address as string);
- addressIdMap[address as string] = address_id as number;
- } catch (e) {
- addressList.push("无效凭证");
- invalidJwtList.push(jwt);
- console.log(`获取地址列表失败: ${(e as Error).message}`);
- }
- }
- return { addressList, addressIdMap, invalidJwtList };
-}
-
-export const bindTelegramAddress = async (
- c: Context, userId: string, jwt: string
-): Promise => {
- const { address } = await Jwt.verify(jwt, c.env.JWT_SECRET, "HS256");
- if (!address) {
- throw Error("无效凭证");
- }
- const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
- const { addressIdMap } = await jwtListToAddressData(c, jwtList);
- if (address as string in addressIdMap) {
- return address as string;
- }
- if (jwtList.length >= getIntValue(c.env.TG_MAX_ADDRESS, 5)) {
- throw Error("绑定地址数量已达上限, 请先 /cleaninvalidaddress");
- }
- await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify([...jwtList, jwt]));
- // for mail push to telegram
- await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${address}`, userId.toString());
- return address as string;
-}
-
-export const unbindTelegramAddress = async (
- c: Context, userId: string, address: string
-): Promise => {
- const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
- const newJwtList = [];
- for (const jwt of jwtList) {
- try {
- const { address: kvAddress } = await Jwt.verify(jwt, c.env.JWT_SECRET, "HS256");
- if (kvAddress == address) {
- continue;
- }
- } catch (e) {
- console.log(`解绑失败: ${(e as Error).message}`);
- }
- newJwtList.push(jwt);
- }
- await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify(newJwtList));
- await c.env.KV.delete(`${CONSTANTS.TG_KV_PREFIX}:${address}`);
- return true;
-}
-
-export const unbindTelegramByAddress = async (
- c: Context, address: string
-): Promise => {
- if (!c.env.KV) return true;
- const userId = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${address}`)
- if (userId) {
- return await unbindTelegramAddress(c, userId, address);
- }
- return true;
-}
-
-
-export const deleteTelegramAddress = async (
- c: Context, userId: string, address: string
-): Promise => {
- const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
- const { addressIdMap } = await jwtListToAddressData(c, jwtList);
- if (!(address in addressIdMap)) {
- throw Error("此地址不属于您");
- }
- await deleteAddressWithData(c, null, addressIdMap[address])
- return true;
-}
diff --git a/cloudflare_temp_email/worker/src/telegram_api/index.ts b/cloudflare_temp_email/worker/src/telegram_api/index.ts
deleted file mode 100644
index 4a3fc02..0000000
--- a/cloudflare_temp_email/worker/src/telegram_api/index.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { Hono } from 'hono'
-import { ServerResponse } from 'node:http'
-import { Writable } from 'node:stream'
-
-import { newTelegramBot, initTelegramBotCommands, sendMailToTelegram } from './telegram'
-import settings from './settings'
-import miniapp from './miniapp'
-
-export const api = new Hono();
-export { sendMailToTelegram }
-
-api.use("/telegram/*", async (c, next) => {
- if (!c.env.TELEGRAM_BOT_TOKEN) {
- return c.text("TELEGRAM_BOT_TOKEN is required", 400);
- }
- if (!c.env.KV) {
- return c.text("KV is required", 400);
- }
- return await next();
-});
-
-api.use("/admin/telegram/*", async (c, next) => {
- if (!c.env.TELEGRAM_BOT_TOKEN) {
- return c.text("TELEGRAM_BOT_TOKEN is required", 400);
- }
- if (!c.env.KV) {
- return c.text("KV is required", 400);
- }
- return await next();
-});
-
-api.post("/telegram/webhook", async (c) => {
- const token = c.env.TELEGRAM_BOT_TOKEN;
- const bot = newTelegramBot(c, token);
- let body = null;
- const res = new Writable();
- Object.assign(res, {
- headersSent: false,
- setHeader: (name: string, value: string) => c.header(name, value),
- end: (data: any) => body = data,
- });
- const reqJson = await c.req.json();
- await bot.handleUpdate(reqJson, res as ServerResponse);
- return c.body(body);
-});
-
-api.post("/admin/telegram/init", async (c) => {
- const domain = new URL(c.req.url).host;
- const token = c.env.TELEGRAM_BOT_TOKEN;
- const webhookUrl = `https://${domain}/telegram/webhook`;
- console.log(`setting webhook to ${webhookUrl}`);
- const bot = newTelegramBot(c, token);
- await bot.telegram.setWebhook(webhookUrl)
- await initTelegramBotCommands(bot);
- return c.json({
- message: "webhook set successfully",
- });
-});
-
-api.get("/admin/telegram/status", async (c) => {
- const token = c.env.TELEGRAM_BOT_TOKEN;
- const bot = newTelegramBot(c, token);
- const info = await bot.telegram.getWebhookInfo()
- const commands = await bot.telegram.getMyCommands()
- return c.json({ info, commands });
-});
-
-api.get("/admin/telegram/settings", settings.getTelegramSettings);
-api.post("/admin/telegram/settings", settings.saveTelegramSettings);
-api.post("/telegram/get_bind_address", miniapp.getTelegramBindAddress);
-api.post("/telegram/new_address", miniapp.newTelegramAddress);
-api.post("/telegram/bind_address", miniapp.bindAddress);
-api.post("/telegram/unbind_address", miniapp.unbindAddress);
-api.post("/telegram/get_mail", miniapp.getMail);
diff --git a/cloudflare_temp_email/worker/src/telegram_api/miniapp.ts b/cloudflare_temp_email/worker/src/telegram_api/miniapp.ts
deleted file mode 100644
index 3dde76b..0000000
--- a/cloudflare_temp_email/worker/src/telegram_api/miniapp.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-import { Context } from "hono";
-import { Jwt } from 'hono/utils/jwt'
-import { CONSTANTS } from "../constants";
-import { bindTelegramAddress, jwtListToAddressData, tgUserNewAddress, unbindTelegramAddress } from "./common";
-import { checkCfTurnstile } from "../utils";
-import { TelegramSettings } from "./settings";
-import i18n from "../i18n";
-
-const encoder = new TextEncoder();
-const TG_AUTH_TIMEOUT = 300;
-
-const checkTelegramAuth = async (
- c: Context, initData: string
-): Promise => {
- // check if the request is from telegram
- const initDataObj = new URLSearchParams(initData);
- initDataObj.sort()
- const hash = initDataObj.get('hash');
- initDataObj.delete("hash");
- const dataToCheck = [...initDataObj.entries()].map(([key, value]) => key + "=" + value).join("\n");
- const auth_date = Number(initDataObj.get('auth_date'));
- if (auth_date + TG_AUTH_TIMEOUT < (new Date().getTime() / 1000)) {
- throw Error("Auth date expired");
- }
- const user = initDataObj.get('user');
- if (!hash || !user) {
- throw Error("Invalid initData");
- }
- const { id: userId } = JSON.parse(user);
- const cryptoKey = await crypto.subtle.importKey(
- "raw",
- encoder.encode("WebAppData"),
- { name: "HMAC", hash: { name: "SHA-256" } },
- false,
- ["sign"]
- );
- const secretKeyBuffer = await crypto.subtle.sign(
- "HMAC", cryptoKey, encoder.encode(c.env.TELEGRAM_BOT_TOKEN)
- );
- const secretKey = await crypto.subtle.importKey(
- "raw",
- secretKeyBuffer,
- { name: "HMAC", hash: { name: "SHA-256" } },
- false,
- ["sign", "verify"]
- );
- const calcHmac = await crypto.subtle.sign(
- "HMAC", secretKey, encoder.encode(dataToCheck)
- );
- const calcHash = Array.from(new Uint8Array(calcHmac))
- .map((b) => b.toString(16).padStart(2, "0"))
- .join("");
- if (calcHash != hash) {
- throw Error("Invalid initData");
- }
- if (typeof userId === "number") {
- return userId.toString();
- }
- return userId;
-}
-
-async function getTelegramBindAddress(c: Context): Promise {
- const { initData } = await c.req.json();
- try {
- const userId = await checkTelegramAuth(c, initData);
- // get the address list from the KV
- const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
- const res = [];
- for (const jwt of jwtList) {
- try {
- const { address } = await Jwt.verify(jwt, c.env.JWT_SECRET, "HS256");
- res.push({ address, jwt });
- } catch (e) {
- console.error(`failed to verify jwt with error: ${e}`)
- continue;
- }
- }
- return c.json(res);
- }
- catch (e) {
- return c.text((e as Error).message, 400);
- }
-}
-
-async function newTelegramAddress(c: Context): Promise {
- const { initData, address, cf_token } = await c.req.json();
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- // check cf turnstile
- try {
- await checkCfTurnstile(c, cf_token);
- } catch (error) {
- return c.text(msgs.TurnstileCheckFailedMsg, 500)
- }
- try {
- const userId = await checkTelegramAuth(c, initData);
- // get the address list from the KV
- const res = await tgUserNewAddress(c, userId, address)
- return c.json(res);
- }
- catch (e) {
- return c.text((e as Error).message, 400);
- }
-}
-
-async function bindAddress(c: Context): Promise {
- const { initData, jwt } = await c.req.json();
- try {
- const userId = await checkTelegramAuth(c, initData);
- await bindTelegramAddress(c, userId, jwt);
- return c.json({ success: true });
- }
- catch (e) {
- return c.text((e as Error).message, 400);
- }
-}
-
-async function unbindAddress(c: Context): Promise {
- const { initData, address } = await c.req.json();
- try {
- const userId = await checkTelegramAuth(c, initData);
- await unbindTelegramAddress(c, userId, address);
- return c.json({ success: true });
- }
- catch (e) {
- return c.text((e as Error).message, 400);
- }
-}
-
-async function getMail(c: Context): Promise {
- const { initData, mailId } = await c.req.json();
- try {
- const userId = await checkTelegramAuth(c, initData);
- const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
- const { addressList, addressIdMap } = await jwtListToAddressData(c, jwtList);
- const result = await c.env.DB.prepare(
- `SELECT * FROM raw_mails where id = ?`
- ).bind(mailId).first();
- const settings = await c.env.KV.get(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
- const superUser = settings?.enableGlobalMailPush && settings?.globalMailPushList.includes(userId);
- if (!superUser) {
- if (result?.address && !(result.address as string in addressIdMap)) {
- return c.text("无权查看此邮件", 403);
- }
- const address_id = addressIdMap[result?.address as string];
- const db_address_id = await c.env.DB.prepare(
- `SELECT id FROM address where id = ? `
- ).bind(address_id).first("id");
- if (!db_address_id) {
- return c.text("无权查看此邮件", 403);
- }
- }
- return c.json(result);
- }
- catch (e) {
- return c.text((e as Error).message, 400);
- }
-}
-
-export default {
- getTelegramBindAddress,
- newTelegramAddress,
- bindAddress,
- unbindAddress,
- getMail,
-}
diff --git a/cloudflare_temp_email/worker/src/telegram_api/settings.ts b/cloudflare_temp_email/worker/src/telegram_api/settings.ts
deleted file mode 100644
index 193e858..0000000
--- a/cloudflare_temp_email/worker/src/telegram_api/settings.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { Context } from "hono";
-import { CONSTANTS } from "../constants";
-
-export class TelegramSettings {
- enableAllowList: boolean;
- allowList: string[];
- miniAppUrl: string;
- enableGlobalMailPush: boolean;
- globalMailPushList: string[];
-
- constructor(
- enableAllowList: boolean, allowList: string[], miniAppUrl: string,
- enableGlobalMailPush: boolean, globalMailPushList: string[]
- ) {
- this.enableAllowList = enableAllowList;
- this.allowList = allowList;
- this.miniAppUrl = miniAppUrl;
- this.enableGlobalMailPush = enableGlobalMailPush;
- this.globalMailPushList = globalMailPushList;
- }
-}
-
-async function getTelegramSettings(c: Context): Promise {
- const settings = await c.env.KV.get(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
- return c.json(settings || new TelegramSettings(false, [], "", false, []));
-}
-
-
-async function saveTelegramSettings(c: Context): Promise {
- const settings = await c.req.json();
- await c.env.KV.put(CONSTANTS.TG_KV_SETTINGS_KEY, JSON.stringify(settings));
- return c.json({ success: true })
-}
-
-export default {
- getTelegramSettings,
- saveTelegramSettings,
-}
diff --git a/cloudflare_temp_email/worker/src/telegram_api/telegram.ts b/cloudflare_temp_email/worker/src/telegram_api/telegram.ts
deleted file mode 100644
index ce7fd77..0000000
--- a/cloudflare_temp_email/worker/src/telegram_api/telegram.ts
+++ /dev/null
@@ -1,375 +0,0 @@
-
-import { Context } from "hono";
-import { Telegraf, Context as TgContext, Markup } from "telegraf";
-import { callbackQuery } from "telegraf/filters";
-
-import { CONSTANTS } from "../constants";
-import { getDomains, getJsonObjectValue, getStringValue } from '../utils';
-import { TelegramSettings } from "./settings";
-import { bindTelegramAddress, deleteTelegramAddress, jwtListToAddressData, tgUserNewAddress, unbindTelegramAddress, unbindTelegramByAddress } from "./common";
-import { commonParseMail } from "../common";
-import { UserFromGetMe } from "telegraf/types";
-
-
-const COMMANDS = [
- {
- command: "start",
- description: "开始使用"
- },
- {
- command: "new",
- description: "新建邮箱地址, 如果要自定义邮箱地址, 请输入 /new, 通过 /new @ 可以指定, name [a-z0-9] 有效, name 为空则随机生成, @ 可选"
- },
- {
- command: "address",
- description: "查看邮箱地址列表"
- },
- {
- command: "bind",
- description: "绑定邮箱地址, 请输入 /bind <邮箱地址凭证>"
- },
- {
- command: "unbind",
- description: "解绑邮箱地址, 请输入 /unbind <邮箱地址>"
- },
- {
- command: "delete",
- description: "删除邮箱地址, 请输入 /delete <邮箱地址>"
- },
- {
- command: "mails",
- description: "查看邮件, 请输入 /mails <邮箱地址>, 不输入地址默认查看第一个地址"
- },
- {
- command: "cleaninvalidaddress",
- description: "清理无效地址, 请输入 /cleaninvalidaddress"
- },
-]
-
-export function newTelegramBot(c: Context, token: string): Telegraf {
- const bot = new Telegraf(token);
- const botInfo = getJsonObjectValue(c.env.TG_BOT_INFO);
- if (botInfo) {
- bot.botInfo = botInfo;
- }
-
- bot.use(async (ctx, next) => {
- // check if in private chat
- if (ctx.chat?.type !== "private") {
- return;
- }
-
- const userId = ctx?.message?.from?.id || ctx.callbackQuery?.message?.chat?.id;
- if (!userId) {
- return await ctx.reply("无法获取用户信息");
- }
-
- const settings = await c.env.KV.get(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
- if (settings?.enableAllowList && settings?.enableAllowList
- && !settings.allowList.includes(userId.toString())
- ) {
- return await ctx.reply("您没有权限使用此机器人");
- }
- try {
- await next();
- } catch (error) {
- console.error(`Error: ${error}`);
- return await ctx.reply(`Error: ${error}`);
- }
- })
-
- bot.command("start", async (ctx: TgContext) => {
- const prefix = getStringValue(c.env.PREFIX)
- const domains = getDomains(c);
- return await ctx.reply(
- "欢迎使用本机器人, 您可以打开 mini app \n\n"
- + (prefix ? `当前已启用前缀: ${prefix}\n` : '')
- + `当前可用域名: ${JSON.stringify(domains)}\n`
- + "请使用以下命令:\n"
- + COMMANDS.map(c => `/${c.command}: ${c.description}`).join("\n")
- );
- });
-
- bot.command("new", async (ctx: TgContext) => {
- const userId = ctx?.message?.from?.id;
- if (!userId) {
- return await ctx.reply("无法获取用户信息");
- }
- try {
- // @ts-ignore
- const address = ctx?.message?.text.slice("/new".length).trim();
- const res = await tgUserNewAddress(c, userId.toString(), address);
- return await ctx.reply(`创建地址成功:\n`
- + `地址: ${res.address}\n`
- + `凭证: \`${res.jwt}\`\n`,
- {
- parse_mode: "Markdown"
- }
- );
- } catch (e) {
- return await ctx.reply(`创建地址失败: ${(e as Error).message}`);
- }
- });
-
- bot.command("bind", async (ctx: TgContext) => {
- const userId = ctx?.message?.from?.id;
- if (!userId) {
- return await ctx.reply("无法获取用户信息");
- }
- try {
- // @ts-ignore
- const jwt = ctx?.message?.text.slice("/bind".length).trim();
- if (!jwt) {
- return await ctx.reply("请输入凭证");
- }
- const address = await bindTelegramAddress(c, userId.toString(), jwt);
- return await ctx.reply(`绑定成功:\n`
- + `地址: ${address}`
- );
- }
- catch (e) {
- return await ctx.reply(`绑定失败: ${(e as Error).message}`);
- }
- });
-
- bot.command("unbind", async (ctx: TgContext) => {
- const userId = ctx?.message?.from?.id;
- if (!userId) {
- return await ctx.reply("无法获取用户信息");
- }
- try {
- // @ts-ignore
- const address = ctx?.message?.text.slice("/unbind".length).trim();
- if (!address) {
- return await ctx.reply("请输入地址");
- }
- await unbindTelegramAddress(c, userId.toString(), address);
- return await ctx.reply(`解绑成功:\n地址: ${address}`
- );
- }
- catch (e) {
- return await ctx.reply(`解绑失败: ${(e as Error).message}`);
- }
- })
-
- bot.command("delete", async (ctx: TgContext) => {
- const userId = ctx?.message?.from?.id;
- if (!userId) {
- return await ctx.reply("无法获取用户信息");
- }
- try {
- // @ts-ignore
- const address = ctx?.message?.text.slice("/delete".length).trim();
- if (!address) {
- return await ctx.reply("请输入地址");
- }
- await deleteTelegramAddress(c, userId.toString(), address);
- return await ctx.reply(`删除成功: ${address}`);
- } catch (e) {
- return await ctx.reply(`删除失败: ${(e as Error).message}`);
- }
- });
-
- bot.command("address", async (ctx) => {
- const userId = ctx?.message?.from?.id;
- if (!userId) {
- return await ctx.reply("无法获取用户信息");
- }
- try {
- const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
- const { addressList } = await jwtListToAddressData(c, jwtList);
- return await ctx.reply(`地址列表:\n\n`
- + addressList.map(a => `地址: ${a}`).join("\n")
- );
- } catch (e) {
- return await ctx.reply(`获取地址列表失败: ${(e as Error).message}`);
- }
- });
-
- bot.command("cleaninvalidaddress", async (ctx: TgContext) => {
- const userId = ctx?.message?.from?.id;
- if (!userId) {
- return await ctx.reply("无法获取用户信息");
- }
- try {
- const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
- const { invalidJwtList } = await jwtListToAddressData(c, jwtList);
- const newJwtList = jwtList.filter(jwt => !invalidJwtList.includes(jwt));
- await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify(newJwtList));
- const { addressList } = await jwtListToAddressData(c, newJwtList);
- return await ctx.reply(`清理无效地址成功:\n\n`
- + `当前地址列表:\n\n`
- + addressList.map(a => `地址: ${a}`).join("\n")
- );
- } catch (e) {
- return await ctx.reply(`清理无效地址失败: ${(e as Error).message}`);
- }
- });
-
- const queryMail = async (ctx: TgContext, queryAddress: string, mailIndex: number, edit: boolean) => {
- const userId = ctx?.message?.from?.id || ctx.callbackQuery?.message?.chat?.id;
- if (!userId) {
- return await ctx.reply("无法获取用户信息");
- }
- const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
- const { addressList, addressIdMap } = await jwtListToAddressData(c, jwtList);
- if (!queryAddress && addressList.length > 0) {
- queryAddress = addressList[0];
- }
- if (!(queryAddress in addressIdMap)) {
- return await ctx.reply(`未绑定此地址 ${queryAddress}`);
- }
- const address_id = addressIdMap[queryAddress];
- const db_address_id = await c.env.DB.prepare(
- `SELECT id FROM address where id = ? `
- ).bind(address_id).first("id");
- if (!db_address_id) {
- return await ctx.reply("无效地址");
- }
- const { raw, id: mailId, created_at } = await c.env.DB.prepare(
- `SELECT * FROM raw_mails where address = ? `
- + ` order by id desc limit 1 offset ?`
- ).bind(
- queryAddress, mailIndex
- ).first<{ raw: string, id: string, created_at: string }>() || {};
- const { mail } = raw ? await parseMail({ rawEmail: raw }, queryAddress, created_at) : { mail: "已经没有邮件了" };
- const settings = await c.env.KV.get(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
- const miniAppButtons = []
- if (settings?.miniAppUrl && settings?.miniAppUrl?.length > 0 && mailId) {
- const url = new URL(settings.miniAppUrl);
- url.pathname = "/telegram_mail"
- url.searchParams.set("mail_id", mailId);
- miniAppButtons.push(Markup.button.webApp("查看邮件", url.toString()));
- }
- if (edit) {
- return await ctx.editMessageText(mail || "无邮件",
- {
- ...Markup.inlineKeyboard([
- Markup.button.callback("上一条", `mail_${queryAddress}_${mailIndex - 1}`, mailIndex <= 0),
- ...miniAppButtons,
- Markup.button.callback("下一条", `mail_${queryAddress}_${mailIndex + 1}`, !raw),
- ])
- },
- );
- }
- return await ctx.reply(mail || "无邮件",
- {
- ...Markup.inlineKeyboard([
- Markup.button.callback("上一条", `mail_${queryAddress}_${mailIndex - 1}`, mailIndex <= 0),
- ...miniAppButtons,
- Markup.button.callback("下一条", `mail_${queryAddress}_${mailIndex + 1}`, !raw),
- ])
- },
- );
- }
-
- bot.command("mails", async ctx => {
- try {
- const queryAddress = ctx?.message?.text.slice("/mails".length).trim();
- return await queryMail(ctx, queryAddress, 0, false);
- } catch (e) {
- return await ctx.reply(`获取邮件失败: ${(e as Error).message}`);
- }
- });
-
- bot.on(callbackQuery("data"), async ctx => {
- // Use ctx.callbackQuery.data
- try {
- const data = ctx.callbackQuery.data;
- if (data && data.startsWith("mail_") && data.split("_").length === 3) {
- const [_, queryAddress, mailIndex] = data.split("_");
- await queryMail(ctx, queryAddress, parseInt(mailIndex), true);
- }
- }
- catch (e) {
- console.log(`获取邮件失败: ${(e as Error).message}`, e);
- return await ctx.answerCbQuery(`获取邮件失败: ${(e as Error).message}`);
- }
- await ctx.answerCbQuery();
- });
-
- return bot;
-}
-
-
-export async function initTelegramBotCommands(bot: Telegraf) {
- await bot.telegram.setMyCommands(COMMANDS);
-}
-
-const parseMail = async (
- parsedEmailContext: ParsedEmailContext,
- address: string, created_at: string | undefined | null
-) => {
- if (!parsedEmailContext.rawEmail) {
- return {};
- }
- try {
- const parsedEmail = await commonParseMail(parsedEmailContext);
- let parsedText = parsedEmail?.text || "";
- if (parsedText.length && parsedText.length > 1000) {
- parsedText = parsedEmail?.text.substring(0, 1000) + "\n\n...\n消息过长请到miniapp查看";
- }
- return {
- isHtml: false,
- mail: `From: ${parsedEmail?.sender || "无发件人"}\n`
- + `To: ${address}\n`
- + (created_at ? `Date: ${created_at}\n` : "")
- + `Subject: ${parsedEmail?.subject}\n`
- + `Content:\n${parsedText || "解析失败,请打开 mini app 查看"}`
- };
- } catch (e) {
- return {
- isHtml: false,
- mail: `解析邮件失败: ${(e as Error).message}`
- };
- }
-}
-
-
-export async function sendMailToTelegram(
- c: Context, address: string,
- parsedEmailContext: ParsedEmailContext,
- message_id: string | null
-) {
- if (!c.env.TELEGRAM_BOT_TOKEN || !c.env.KV) {
- return;
- }
- const userId = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${address}`);
- const { mail } = await parseMail(parsedEmailContext, address, new Date().toUTCString());
- if (!mail) {
- return;
- }
- const settings = await c.env.KV.get(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
- const globalPush = settings?.enableGlobalMailPush && settings?.globalMailPushList;
- if (!userId && !globalPush) {
- return;
- }
- const mailId = await c.env.DB.prepare(
- `SELECT id FROM raw_mails where address = ? and message_id = ?`
- ).bind(address, message_id).first("id");
- const bot = newTelegramBot(c, c.env.TELEGRAM_BOT_TOKEN);
- const miniAppButtons = []
- if (settings?.miniAppUrl && settings?.miniAppUrl?.length > 0 && mailId) {
- const url = new URL(settings.miniAppUrl);
- url.pathname = "/telegram_mail"
- url.searchParams.set("mail_id", mailId);
- miniAppButtons.push(Markup.button.webApp("查看邮件", url.toString()));
- }
- if (globalPush) {
- for (const pushId of settings.globalMailPushList) {
- await bot.telegram.sendMessage(pushId, mail, {
- ...Markup.inlineKeyboard([
- ...miniAppButtons,
- ])
- });
- }
- }
- if (!userId) {
- return;
- }
- await bot.telegram.sendMessage(userId, mail, {
- ...Markup.inlineKeyboard([
- ...miniAppButtons,
- ])
- });
-}
diff --git a/cloudflare_temp_email/worker/src/types.d.ts b/cloudflare_temp_email/worker/src/types.d.ts
deleted file mode 100644
index e559d5b..0000000
--- a/cloudflare_temp_email/worker/src/types.d.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-type UserRole = {
- domains: string[] | undefined | null,
- role: string,
- prefix: string | undefined | null
-}
-
-type Bindings = {
- // bindings
- DB: D1Database
- KV: KVNamespace
- RATE_LIMITER: any
- SEND_MAIL: any
- ASSETS: Fetcher
-
- // config
- DEFAULT_LANG: string | undefined
- TITLE: string | undefined
- ANNOUNCEMENT: string | undefined | null
- ALWAYS_SHOW_ANNOUNCEMENT: string | boolean | undefined
- PREFIX: string | undefined
- ADDRESS_CHECK_REGEX: string | undefined
- ADDRESS_REGEX: string | undefined
- MIN_ADDRESS_LEN: string | number | undefined
- MAX_ADDRESS_LEN: string | number | undefined
- DEFAULT_DOMAINS: string | string[] | undefined
- DOMAINS: string | string[] | undefined
- DISABLE_CUSTOM_ADDRESS_NAME: string | boolean | undefined
- CREATE_ADDRESS_DEFAULT_DOMAIN_FIRST: string | boolean | undefined
- ADMIN_USER_ROLE: string | undefined
- USER_DEFAULT_ROLE: string | UserRole | undefined
- USER_ROLES: string | UserRole[] | undefined
- DOMAIN_LABELS: string | string[] | undefined
- PASSWORDS: string | string[] | undefined
- ADMIN_PASSWORDS: string | string[] | undefined
- DISABLE_ADMIN_PASSWORD_CHECK: string | boolean | undefined
- JWT_SECRET: string
- BLACK_LIST: string | undefined
- ENABLE_AUTO_REPLY: string | boolean | undefined
- ENABLE_WEBHOOK: string | boolean | undefined
- ENABLE_USER_CREATE_EMAIL: string | boolean | undefined
- DISABLE_ANONYMOUS_USER_CREATE_EMAIL: string | boolean | undefined
- ENABLE_USER_DELETE_EMAIL: string | boolean | undefined
- ENABLE_INDEX_ABOUT: string | boolean | undefined
- DEFAULT_SEND_BALANCE: number | string | undefined
- NO_LIMIT_SEND_ROLE: string | undefined | null
- ADMIN_CONTACT: string | undefined
- COPYRIGHT: string | undefined
- DISABLE_SHOW_GITHUB: string | boolean | undefined
- FORWARD_ADDRESS_LIST: string | string[] | undefined
-
- ENABLE_CHECK_JUNK_MAIL: string | boolean | undefined
- JUNK_MAIL_CHECK_LIST: string | string[] | undefined
- JUNK_MAIL_FORCE_PASS_LIST: string | string[] | undefined
-
- ENABLE_ANOTHER_WORKER: string | boolean | undefined
- ANOTHER_WORKER_LIST: string | AnotherWorker[] | undefined
-
- SUBDOMAIN_FORWARD_ADDRESS_LIST: string | SubdomainForwardAddressList[] | undefined
-
- REMOVE_ALL_ATTACHMENT: string | boolean | undefined
- REMOVE_EXCEED_SIZE_ATTACHMENT: string | boolean | undefined
-
- // s3 config
- S3_ENDPOINT: string | undefined
- S3_ACCESS_KEY_ID: string | undefined
- S3_SECRET_ACCESS_KEY: string | undefined
- S3_BUCKET: string | undefined
- S3_URL_EXPIRES: number | undefined
-
- // cf turnstile
- CF_TURNSTILE_SITE_KEY: string | undefined
- CF_TURNSTILE_SECRET_KEY: string | undefined
-
- // resend
- RESEND_TOKEN: string | undefined
- [key: `RESEND_TOKEN_${string}`]: string | undefined
-
- // SMTP config
- SMTP_CONFIG: string | object | undefined
-
- // telegram config
- TELEGRAM_BOT_TOKEN: string
- TG_MAX_ADDRESS: number | undefined
- TG_BOT_INFO: string | object | undefined
-
- // webhook config
- FRONTEND_URL: string | undefined
-}
-
-type JwtPayload = {
- address: string
- address_id: number
-}
-
-type UserPayload = {
- user_email: string
- user_id: number
- exp: number
- iat: number
-}
-
-type Variables = {
- userPayload: UserPayload,
- userRolePayload: string | undefined | null,
- jwtPayload: JwtPayload,
- lang: string | undefined | null
-}
-
-type HonoCustomType = {
- "Bindings": Bindings;
- "Variables": Variables;
-}
-
-type AnotherWorker = {
- binding: string | undefined | null,
- method: string | undefined | null,
- keywords: string[] | undefined | null
-}
-
-type RPCEmailMessage = {
- from: string | undefined | null,
- to: string | undefined | null,
- rawEmail: string | undefined | null,
- headers: object | undefined | null,
-}
-
-type ParsedEmailContext = {
- rawEmail: string,
- parsedEmail?: {
- sender: string,
- subject: string,
- text: string,
- html: string,
- headers?: Record[]
- } | undefined
-}
-
-type SubdomainForwardAddressList = {
- domains: string[] | undefined | null,
- forward: string,
-}
diff --git a/cloudflare_temp_email/worker/src/user_api/bind_address.ts b/cloudflare_temp_email/worker/src/user_api/bind_address.ts
deleted file mode 100644
index 7ece5d3..0000000
--- a/cloudflare_temp_email/worker/src/user_api/bind_address.ts
+++ /dev/null
@@ -1,268 +0,0 @@
-import { Context } from 'hono';
-import { Jwt } from 'hono/utils/jwt'
-
-import { UserSettings } from "../models";
-import { getJsonSetting } from "../utils"
-import { CONSTANTS } from "../constants";
-import { unbindTelegramByAddress } from '../telegram_api/common';
-import i18n from '../i18n';
-import { updateAddressUpdatedAt } from '../common';
-
-const UserBindAddressModule = {
- bind: async (c: Context) => {
- const { user_id } = c.get("userPayload");
- const { address_id } = c.get("jwtPayload");
- return await UserBindAddressModule.bindByID(c, user_id, address_id)
- },
- bindByID: async (
- c: Context,
- user_id: number | string, address_id: number | string
- ) => {
- if (!address_id || !user_id) {
- return c.text("No address or user token", 400)
- }
- // check if address exists
- const db_address_id = await c.env.DB.prepare(
- `SELECT id FROM address where id = ?`
- ).bind(address_id).first("id");
- if (!db_address_id) {
- return c.text("Address not found", 400)
- }
- // check if user exists
- const db_user_id = await c.env.DB.prepare(
- `SELECT id FROM users where id = ?`
- ).bind(user_id).first("id");
- if (!db_user_id) {
- return c.text("User not found", 400)
- }
- // check if binded
- const db_user_address_id = await c.env.DB.prepare(
- `SELECT user_id FROM users_address where user_id = ? and address_id = ?`
- ).bind(user_id, address_id).first("user_id");
- if (db_user_address_id) return c.json({ success: true })
- // check if binded address count
- const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
- const settings = new UserSettings(value);
- if (settings.maxAddressCount > 0) {
- const { count } = await c.env.DB.prepare(
- `SELECT COUNT(*) as count FROM users_address where user_id = ?`
- ).bind(user_id).first<{ count: number }>() || { count: 0 };
- if (count >= settings.maxAddressCount) {
- return c.text("Max address count reached", 400)
- }
- }
- // bind
- try {
- const { success } = await c.env.DB.prepare(
- `INSERT INTO users_address (user_id, address_id) VALUES (?, ?)`
- ).bind(user_id, address_id).run();
- if (!success) {
- return c.text("Failed to bind", 500)
- }
- } catch (e) {
- const error = e as Error;
- if (error.message && error.message.includes("UNIQUE")) {
- return c.text("Address already binded, please unbind first", 400)
- }
- return c.text("Failed to bind", 500)
- }
- return c.json({ success: true })
- },
- unbind: async (c: Context) => {
- const { user_id } = c.get("userPayload");
- const { address_id } = await c.req.json();
- if (!address_id || !user_id) {
- return c.text("Invalid address or user token", 400)
- }
- // check if address exists
- const db_address_id = await c.env.DB.prepare(
- `SELECT id FROM address where id = ?`
- ).bind(address_id).first("id");
- if (!db_address_id) {
- return c.text("Address not found", 400)
- }
- // check if user exists
- const db_user_id = await c.env.DB.prepare(
- `SELECT id FROM users where id = ?`
- ).bind(user_id).first("id");
- if (!db_user_id) {
- return c.text("User not found", 400)
- }
- // unbind
- try {
- const { success } = await c.env.DB.prepare(
- `DELETE FROM users_address where user_id = ? and address_id = ?`
- ).bind(user_id, address_id).run();
- if (!success) {
- return c.text("Failed to unbind", 500)
- }
- } catch (e) {
- return c.text("Failed to unbind", 500)
- }
- return c.json({ success: true })
- },
- getBindedAddresses: async (c: Context) => {
- const { user_id } = c.get("userPayload");
- const results = await UserBindAddressModule.getBindedAddressesById(c, user_id);
- return c.json({
- results: results,
- });
- },
- getBindedAddressListById: async (
- c: Context, user_id: number | string
- ): Promise => {
- const bindedAddressList = await UserBindAddressModule.getBindedAddressesById(c, user_id);
- return bindedAddressList.map((item) => item.name);
- },
- getBindedAddressesById: async (
- c: Context, user_id: number | string
- ): Promise<{
- id: number;
- name: string;
- mail_count: number;
- send_count: number;
- created_at: string;
- updated_at: string;
- }[]> => {
- const msgs = i18n.getMessagesbyContext(c);
- if (!user_id) {
- throw new Error(msgs.UserNotFoundMsg);
- }
- // select binded address
- const { results } = await c.env.DB.prepare(
- `SELECT a.*,`
- + ` (SELECT COUNT(*) FROM raw_mails WHERE address = a.name) AS mail_count,`
- + ` (SELECT COUNT(*) FROM sendbox WHERE address = a.name) AS send_count`
- + ` FROM address a `
- + ` JOIN users_address ua `
- + ` ON ua.address_id = a.id `
- + ` WHERE ua.user_id = ?`
- + ` ORDER BY a.id DESC`
- ).bind(user_id).all<{
- id: number;
- name: string;
- mail_count: number;
- send_count: number;
- created_at: string;
- updated_at: string;
- }>();
- return results || [];
- },
- getBindedAddressJwt: async (c: Context) => {
- const { address_id } = c.req.param();
- // check binded
- const { user_id } = c.get("userPayload");
- if (!address_id || !user_id) {
- return c.text("Invalid address or user token", 400)
- }
- // check users_address if address binded
- const db_user_id = await c.env.DB.prepare(
- `SELECT user_id FROM users_address WHERE address_id = ? and user_id = ?`
- ).bind(address_id, user_id).first("user_id");
- if (!db_user_id) {
- return c.text("Address not binded", 400)
- }
- // generate jwt
- const name = await c.env.DB.prepare(
- `SELECT name FROM address WHERE id = ? `
- ).bind(address_id).first("name");
- const jwt = await Jwt.sign({
- address: name,
- address_id: address_id
- }, c.env.JWT_SECRET, "HS256")
- return c.json({
- jwt: jwt
- })
- },
- transferAddress: async (c: Context) => {
- const { user_id } = c.get("userPayload");
- const { address_id, target_user_email } = await c.req.json();
- // check if address exists
- const address = await c.env.DB.prepare(
- `SELECT name FROM address where id = ?`
- ).bind(address_id).first("name");
- if (!address) {
- return c.text("Address not found", 400)
- }
- // check if user exists
- const db_user_id = await c.env.DB.prepare(
- `SELECT id FROM users where id = ?`
- ).bind(user_id).first("id");
- if (!db_user_id) {
- return c.text("User not found", 400)
- }
- // check if target user exists
- const target_user_id = await c.env.DB.prepare(
- `SELECT id FROM users where user_email = ?`
- ).bind(target_user_email).first("id");
- if (!target_user_id) {
- return c.text("Target user not found", 400)
- }
- // check target user binded address count
- const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
- const settings = new UserSettings(value);
- if (settings.maxAddressCount > 0) {
- const { count } = await c.env.DB.prepare(
- `SELECT COUNT(*) as count FROM users_address where user_id = ?`
- ).bind(target_user_id).first<{ count: number }>() || { count: 0 };
- if (count >= settings.maxAddressCount) {
- return c.text("Target User Max address count reached", 400)
- }
- }
- // check if binded
- const db_user_address_id = await c.env.DB.prepare(
- `SELECT user_id FROM users_address where user_id = ? and address_id = ?`
- ).bind(user_id, address_id).first("user_id");
- if (!db_user_address_id) return c.text("Address not binded", 400)
- // unbind telegram address
- await unbindTelegramByAddress(c, address);
- // unbind user address
- try {
- const { success } = await c.env.DB.prepare(
- `DELETE FROM users_address where user_id = ? and address_id = ?`
- ).bind(user_id, address_id).run();
- if (!success) {
- return c.text("Failed to unbind", 500)
- }
- } catch (e) {
- return c.text("Failed to unbind user", 500)
- }
- // delete address
- await c.env.DB.prepare(
- `DELETE FROM address WHERE id = ? `
- ).bind(address_id).run();
- // new address
- const { success: newAddressSuccess } = await c.env.DB.prepare(
- `INSERT INTO address(name) VALUES(?)`
- ).bind(address).run();
- if (!newAddressSuccess) {
- throw new Error("Failed to create address")
- }
- await updateAddressUpdatedAt(c, address);
- // find new address id
- const new_address_id = await c.env.DB.prepare(
- `SELECT id FROM address WHERE name = ?`
- ).bind(address).first("id");
- if (!new_address_id) {
- throw new Error("Failed to find new address id")
- }
- // bind
- try {
- const { success } = await c.env.DB.prepare(
- `INSERT INTO users_address (user_id, address_id) VALUES (?, ?)`
- ).bind(target_user_id, new_address_id).run();
- if (!success) {
- return c.text("Failed to bind", 500)
- }
- } catch (e) {
- const error = e as Error;
- if (error.message && error.message.includes("UNIQUE")) {
- return c.text("Address already binded, please unbind first", 400)
- }
- return c.text("Failed to bind", 500)
- }
- return c.json({ success: true })
- }
-}
-
-export default UserBindAddressModule;
diff --git a/cloudflare_temp_email/worker/src/user_api/index.ts b/cloudflare_temp_email/worker/src/user_api/index.ts
deleted file mode 100644
index 9ecb58c..0000000
--- a/cloudflare_temp_email/worker/src/user_api/index.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { Hono } from 'hono';
-
-import settings from './settings';
-import user from './user';
-import bind_address from './bind_address';
-import passkey from './passkey';
-import oauth2 from './oauth2';
-import user_mail_api from './user_mail_api';
-
-export const api = new Hono();
-
-// settings api
-api.get('/user_api/open_settings', settings.openSettings);
-api.get('/user_api/settings', settings.settings);
-
-// mail api
-api.get('/user_api/mails', user_mail_api.getMails);
-api.delete('/user_api/mails/:id', user_mail_api.deleteMail);
-
-// user api
-api.post('/user_api/login', user.login);
-api.post('/user_api/verify_code', user.verifyCode);
-api.post('/user_api/register', user.register);
-
-// oauth2 api
-api.get('/user_api/oauth2/login_url', oauth2.getOauth2LoginUrl);
-api.post('/user_api/oauth2/callback', oauth2.oauth2Login);
-
-// bind address api
-api.get('/user_api/bind_address', bind_address.getBindedAddresses);
-api.post('/user_api/bind_address', bind_address.bind);
-api.get('/user_api/bind_address_jwt/:address_id', bind_address.getBindedAddressJwt);
-api.post('/user_api/unbind_address', bind_address.unbind);
-api.post('/user_api/transfer_address', bind_address.transferAddress);
-
-// passkey api
-api.get('/user_api/passkey', passkey.getPassKeys);
-api.post('/user_api/passkey/rename', passkey.renamePassKey);
-api.delete('/user_api/passkey/:passkey_id', passkey.deletePassKey);
-api.post('/user_api/passkey/register_request', passkey.registerRequest);
-api.post('/user_api/passkey/register_response', passkey.registerResponse);
-api.post('/user_api/passkey/authenticate_request', passkey.authenticateRequest);
-api.post('/user_api/passkey/authenticate_response', passkey.authenticateResponse);
diff --git a/cloudflare_temp_email/worker/src/user_api/oauth2.ts b/cloudflare_temp_email/worker/src/user_api/oauth2.ts
deleted file mode 100644
index ded1e37..0000000
--- a/cloudflare_temp_email/worker/src/user_api/oauth2.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import { Context } from 'hono';
-import { Jwt } from 'hono/utils/jwt'
-
-import i18n from '../i18n';
-import { getJsonSetting, getStringValue, getUserRoles } from '../utils';
-import { UserOauth2Settings } from '../models';
-import { CONSTANTS } from '../constants';
-
-
-export default {
- getOauth2LoginUrl: async (c: Context) => {
- const settings = await getJsonSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY);
- const { clientID, state } = c.req.query();
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- const setting = settings?.find(s => s.clientID === clientID);
- if (!setting) {
- return c.text(msgs.Oauth2ClientIDNotFoundMsg, 400);
- }
- const url = `${setting.authorizationURL}?client_id=${setting.clientID}&response_type=code&redirect_uri=${setting.redirectURL}&scope=${setting.scope}&state=${state}`
- return c.json({ url });
- },
- oauth2Login: async (c: Context) => {
- const { clientID, code } = await c.req.json<{ clientID?: string, code?: string }>();
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- if (!clientID || !code) {
- return c.text(msgs.Oauth2CliendIDOrCodeMissingMsg, 400);
- }
- const settings = await getJsonSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY);
- const setting = settings?.find(s => s.clientID === clientID);
- if (!setting) {
- return c.text(msgs.Oauth2ClientIDNotFoundMsg, 400);
- }
- const params = {
- code,
- client_id: setting.clientID,
- client_secret: setting.clientSecret,
- grant_type: 'authorization_code',
- redirect_uri: setting.redirectURL,
- }
- const res = await fetch(setting.accessTokenURL, {
- method: 'POST',
- body: setting.accessTokenFormat === 'json'
- ? JSON.stringify(params) :
- new URLSearchParams(params).toString(),
- headers: {
- 'Content-Type': setting.accessTokenFormat === 'json'
- ? 'application/json'
- : 'application/x-www-form-urlencoded',
- "Accept": "application/json"
- }
- })
- if (!res.ok) {
- console.error(`Failed to get access token: ${res.status} ${res.statusText} ${await res.text()}`)
- return c.text(msgs.Oauth2FailedGetAccessTokenMsg, 400);
- }
- const resJson = await res.json();
- const { access_token, token_type } = resJson as { access_token: string, token_type?: string };
- const userRes = await fetch(setting.userInfoURL, {
- headers: {
- "Authorization": `${token_type || 'Bearer'} ${access_token}`,
- "Accept": "application/json",
- "User-Agent": "Cloudflare Workers"
- }
- })
- if (!userRes.ok) {
- console.error(`Failed to get user info: ${userRes.status} ${userRes.statusText} ${await userRes.text()}`)
- return c.text(msgs.Oauth2FailedGetUserInfoMsg, 400);
- }
- const userInfo = await userRes.json()
-
- const email = await (async () => {
- if (setting.userEmailKey.startsWith("$")) {
- const { JSONPath } = await import('jsonpath-plus');
- const email = JSONPath({
- path: setting.userEmailKey,
- json: userInfo,
- })
- if (email && Array.isArray(email) && email.length > 0) {
- return email[0];
- }
- }
- const { [setting.userEmailKey]: email } = userInfo as { [key: string]: string };
- return email;
- })()
-
- if (!email) {
- return c.text(msgs.Oauth2FailedGetUserEmailMsg, 400);
- }
- // check email in mail allow list
- const mailDomain = email.split("@")[1];
- if (setting.enableMailAllowList && !setting.mailAllowList?.includes(mailDomain)) {
- return c.text(`${msgs.UserMailDomainMustInMsg} ${JSON.stringify(setting.mailAllowList, null, 2)}`, 400)
- }
- // insert or update user
- const { success } = await c.env.DB.prepare(
- `INSERT INTO users (user_email, password, user_info)`
- + ` VALUES (?, '', ?)`
- + ` ON CONFLICT(user_email) DO UPDATE SET updated_at = datetime('now')`
- ).bind(
- email, JSON.stringify(userInfo)
- ).run();
- if (!success) {
- return c.text(msgs.FailedToRegisterMsg, 500)
- }
- const { id: user_id } = await c.env.DB.prepare(
- `SELECT id FROM users where user_email = ?`
- ).bind(email).first() || {};
- if (!user_id) {
- return c.text(msgs.UserNotFoundMsg, 400)
- }
- // process user roles
- const defaultRole = getStringValue(c.env.USER_DEFAULT_ROLE);
- if (defaultRole) {
- const user_roles = getUserRoles(c);
- if (!user_roles.find((r) => r.role === defaultRole)) {
- return c.text(msgs.InvalidUserDefaultRoleMsg, 500);
- }
- // update user roles
- const { success: success2 } = await c.env.DB.prepare(
- `INSERT INTO user_roles (user_id, role_text)`
- + ` VALUES (?, ?)`
- + ` ON CONFLICT(user_id) DO NOTHING`
- ).bind(user_id, defaultRole).run();
- if (!success2) {
- return c.text(msgs.FailedUpdateUserDefaultRoleMsg, 500);
- }
- }
- // create jwt
- const jwt = await Jwt.sign({
- user_email: email,
- user_id: user_id,
- // 90 days expire in seconds
- exp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
- iat: Math.floor(Date.now() / 1000),
- }, c.env.JWT_SECRET, "HS256")
- return c.json({
- jwt: jwt
- })
- }
-}
diff --git a/cloudflare_temp_email/worker/src/user_api/passkey.ts b/cloudflare_temp_email/worker/src/user_api/passkey.ts
deleted file mode 100644
index 876a9f5..0000000
--- a/cloudflare_temp_email/worker/src/user_api/passkey.ts
+++ /dev/null
@@ -1,203 +0,0 @@
-import { Context } from 'hono';
-import { Jwt } from 'hono/utils/jwt'
-import {
- generateRegistrationOptions,
- verifyRegistrationResponse,
- generateAuthenticationOptions,
- verifyAuthenticationResponse
-} from '@simplewebauthn/server';
-
-import { Passkey } from '../models';
-import { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types';
-import { isoBase64URL } from '@simplewebauthn/server/helpers';
-
-export default {
- getPassKeys: async (c: Context) => {
- const user = c.get("userPayload");
- const { results } = await c.env.DB.prepare(
- `SELECT passkey_name, passkey_id, created_at, updated_at FROM user_passkeys WHERE user_id = ?`
- ).bind(user.user_id).all>();
- return c.json(results);
- },
- renamePassKey: async (c: Context) => {
- const user = c.get("userPayload");
- const { passkey_id, passkey_name } = await c.req.json();
- if (!passkey_name || passkey_name.length > 255) {
- return c.text("Invalid passkey name", 400);
- }
- const { success } = await c.env.DB.prepare(
- `UPDATE user_passkeys SET passkey_name = ? WHERE user_id = ? AND passkey_id = ?`
- ).bind(passkey_name, user.user_id, passkey_id).run();
- return c.json({ success });
- },
- deletePassKey: async (c: Context) => {
- const user = c.get("userPayload");
- const { passkey_id } = c.req.param();
- const { success } = await c.env.DB.prepare(
- `DELETE FROM user_passkeys WHERE user_id = ? AND passkey_id = ?`
- ).bind(user.user_id, passkey_id).run();
- return c.json({ success });
- },
- registerRequest: async (c: Context) => {
- const user = c.get("userPayload");
- const { domain } = await c.req.json();
- const { results } = await c.env.DB.prepare(
- `SELECT passkey FROM user_passkeys WHERE user_id = ?`
- ).bind(user.user_id).all>();
- const excludeCredentials = results
- .map((record: any) => JSON.parse(record.passkey) as Passkey)
- .map((passkey: Passkey) => ({
- id: passkey.id,
- transports: passkey.transports,
- }));
- // create challenge with 1 hour expiration
- const challenge = await Jwt.sign({
- user_email: user.user_email,
- user_id: user.user_id,
- iat: Math.floor(Date.now() / 1000),
- }, c.env.JWT_SECRET, "HS256")
- // Use SimpleWebAuthn's handy function to create registration options.
- const options = await generateRegistrationOptions({
- rpName: c.env.TITLE || "Temp Mail",
- rpID: domain,
- userID: new TextEncoder().encode(user.user_id.toString()),
- userName: user.user_email,
- userDisplayName: user.user_email,
- attestationType: 'none',
- excludeCredentials: excludeCredentials,
- challenge: challenge,
- });
-
- return c.json(options);
- },
- registerResponse: async (c: Context) => {
- const user = c.get("userPayload");
- const { credential, origin, passkey_name } = await c.req.json();
- // Verify the registration response
- const verification = await verifyRegistrationResponse({
- response: credential,
- expectedChallenge: async (challenge: string) => {
- const payload = await Jwt.verify(atob(challenge), c.env.JWT_SECRET, "HS256");
- if (!payload || !payload.iat) return false;
- // check iad is not older than 5 minutes
- if (Math.floor(Date.now() / 1000) - payload.iat > 300) return false;
- if (payload.user_id !== user.user_id) return false;
- return true;
- },
- expectedOrigin: origin,
- requireUserVerification: true,
- });
- const { verified, registrationInfo } = verification;
-
- if (!verified || !registrationInfo) {
- return c.text("Registration failed", 400);
- }
-
- const {
- credentialID, credentialPublicKey,
- counter, credentialDeviceType, credentialBackedUp,
- } = registrationInfo;
-
- // Base64URL encode ArrayBuffers.
- const base64PublicKey = isoBase64URL.fromBuffer(credentialPublicKey);
-
- const newPasskey: Passkey = {
- id: credentialID,
- publicKey: base64PublicKey,
- counter,
- deviceType: credentialDeviceType,
- backedUp: credentialBackedUp,
- transports: credential?.response?.transports,
- };
-
- // Store the credential ID in the database
- const { success } = await c.env.DB.prepare(
- `INSERT INTO user_passkeys (user_id, passkey_name, passkey_id, passkey, counter) VALUES (?, ?, ?, ?, ?)`
- ).bind(user.user_id, passkey_name, credentialID, JSON.stringify(newPasskey), counter).run();
-
- return c.json({ success });
- },
- authenticateRequest: async (c: Context) => {
- const { domain } = await c.req.json();
- const challenge = await Jwt.sign({
- domain,
- iat: Math.floor(Date.now() / 1000),
- }, c.env.JWT_SECRET, "HS256")
- const options: PublicKeyCredentialRequestOptionsJSON = await generateAuthenticationOptions({
- rpID: domain,
- challenge: challenge,
- allowCredentials: [],
- });
- return c.json(options);
- },
- authenticateResponse: async (c: Context) => {
- const { domain, credential, origin } = await c.req.json();
- const passkey_id = credential?.id;
- if (!passkey_id) {
- return c.text("Invalid request", 400);
- }
- const { user_id, counter, passkey } = await c.env.DB.prepare(
- `SELECT user_id, counter, passkey FROM user_passkeys WHERE passkey_id = ?`
- ).bind(passkey_id).first<{
- counter: number; passkey: string; user_id: number;
- }>() || {};
- if (!passkey) {
- return c.text("Passkey not found", 404);
- }
- const passkeyData = JSON.parse(passkey) as Passkey;
- // Verify the registration response
- const verification = await verifyAuthenticationResponse({
- response: credential,
- expectedChallenge: async (challenge: string) => {
- const payload = await Jwt.verify(atob(challenge), c.env.JWT_SECRET, "HS256");
- if (!payload || !payload.iat) return false;
- // check iad is not older than 5 minutes
- if (Math.floor(Date.now() / 1000) - payload.iat > 300) return false;
- return true;
- },
- expectedOrigin: origin,
- expectedRPID: domain,
- authenticator: {
- credentialID: passkeyData.id,
- credentialPublicKey: isoBase64URL.toBuffer(passkeyData.publicKey),
- counter: counter || passkeyData.counter,
- transports: passkeyData.transports,
- },
- });
- const { verified, authenticationInfo } = verification;
- if (!verified) {
- return c.text("Authentication failed", 400);
- }
-
- if (authenticationInfo) {
- const { newCounter } = authenticationInfo;
- // Update the counter in the database
- await c.env.DB.prepare(
- `UPDATE user_passkeys SET counter = ? WHERE passkey_id = ?`
- ).bind(newCounter, passkey_id).run();
- }
- // update passkey updated_at
- await c.env.DB.prepare(
- `UPDATE user_passkeys SET updated_at = datetime('now') WHERE passkey_id = ?`
- ).bind(passkey_id).run();
-
- // return jwt
- const { user_email } = await c.env.DB.prepare(
- `SELECT user_email FROM users WHERE id = ?`
- ).bind(user_id).first<{ user_email: string }>() || {};
- if (!user_email) {
- return c.text("User not found", 404);
- }
- // create jwt
- const jwt = await Jwt.sign({
- user_email: user_email,
- user_id: user_id,
- // 90 days expire in seconds
- exp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
- iat: Math.floor(Date.now() / 1000),
- }, c.env.JWT_SECRET, "HS256")
- return c.json({
- jwt: jwt
- })
- },
-}
diff --git a/cloudflare_temp_email/worker/src/user_api/settings.ts b/cloudflare_temp_email/worker/src/user_api/settings.ts
deleted file mode 100644
index 4a59293..0000000
--- a/cloudflare_temp_email/worker/src/user_api/settings.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { Context } from "hono";
-
-import i18n from "../i18n";
-import { UserOauth2Settings, UserSettings } from "../models";
-import { getJsonSetting, getUserRoles } from "../utils"
-import { CONSTANTS } from "../constants";
-import { commonGetUserRole } from "../common";
-import { Jwt } from "hono/utils/jwt";
-
-export default {
- openSettings: async (c: Context) => {
- const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
- const settings = new UserSettings(value);
- const oauth2ClientIDs = [] as { clientID: string, name: string }[];
- try {
- const oauth2Settings = await getJsonSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY);
- oauth2ClientIDs.push(
- ...oauth2Settings?.map(s => ({
- clientID: s.clientID,
- name: s.name
- })) || []
- );
- } catch (e) {
- console.error("Failed to get oauth2 settings", e);
- }
- return c.json({
- enable: settings.enable,
- enableMailVerify: settings.enableMailVerify,
- oauth2ClientIDs: oauth2ClientIDs,
- })
- },
- settings: async (c: Context) => {
- const user = c.get("userPayload");
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- // check if user exists
- const db_user_id = await c.env.DB.prepare(
- `SELECT id FROM users where id = ?`
- ).bind(user.user_id).first("id");
- if (!db_user_id) {
- return c.text(msgs.UserNotFoundMsg, 400);
- }
- const user_role = await commonGetUserRole(c, db_user_id);
- const is_admin = (
- c.env.ADMIN_USER_ROLE
- &&
- c.env.ADMIN_USER_ROLE === user_role?.role
- );
- const access_token = user_role?.role ? await Jwt.sign({
- user_email: user.user_email,
- user_id: user.user_id,
- user_role: user_role.role,
- iat: Math.floor(Date.now() / 1000),
- // 1 hour
- exp: Math.floor(Date.now() / 1000) + 3600,
- }, c.env.JWT_SECRET, "HS256") : null;
- // create new if expired in 7 days
- const new_user_token = user.exp > (
- Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60
- ) ? null : await Jwt.sign({
- user_email: user.user_email,
- user_id: user.user_id,
- // 30 days expire in seconds
- exp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
- iat: Math.floor(Date.now() / 1000),
- }, c.env.JWT_SECRET, "HS256");
- // update address updated_at
- try {
- await c.env.DB.prepare(
- `UPDATE address SET updated_at = datetime('now') where id IN `
- + `(SELECT address_id FROM users_address WHERE user_id = ?)`
- ).bind(user.user_id).run();
-
- } catch (e) {
- console.warn("Failed to update address updated_at")
- }
- return c.json({
- ...user,
- is_admin: is_admin,
- access_token: access_token,
- new_user_token: new_user_token,
- user_role: user_role
- });
- },
-}
diff --git a/cloudflare_temp_email/worker/src/user_api/user.ts b/cloudflare_temp_email/worker/src/user_api/user.ts
deleted file mode 100644
index 8492040..0000000
--- a/cloudflare_temp_email/worker/src/user_api/user.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import { Context } from 'hono';
-import { Jwt } from 'hono/utils/jwt'
-
-import i18n from '../i18n';
-import { checkCfTurnstile, getJsonSetting, checkUserPassword, getUserRoles, getStringValue } from "../utils"
-import { CONSTANTS } from "../constants";
-import { GeoData, UserInfo, UserSettings } from "../models";
-import { sendMail } from "../mails_api/send_mail_api";
-
-export default {
- verifyCode: async (c: Context) => {
- const { email, cf_token } = await c.req.json();
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- // check cf turnstile
- try {
- await checkCfTurnstile(c, cf_token);
- } catch (error) {
- return c.text(msgs.TurnstileCheckFailedMsg, 500)
- }
- const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
- const settings = new UserSettings(value)
- // check mail domain allow list
- const mailDomain = email.split("@")[1];
- if (settings.enableMailAllowList
- && settings.mailAllowList
- && !settings.mailAllowList.includes(mailDomain)
- ) {
- return c.text(`${msgs.UserMailDomainMustInMsg} ${JSON.stringify(settings.mailAllowList, null, 2)}`, 400)
- }
- if (!settings.verifyMailSender) {
- return c.text(msgs.VerifyMailSenderNotSetMsg, 400)
- }
- // check if code exists in KV
- const tmpcode = await c.env.KV.get(`temp-mail:${email}`)
- if (tmpcode) {
- return c.text(msgs.CodeAlreadySentMsg, 400)
- }
- // generate code 6 digits and convert to string
- const code = Math.floor(100000 + Math.random() * 900000).toString();
- // send code to email
- try {
- await sendMail(c, settings.verifyMailSender, {
- from_name: "Temp Mail Verify",
- to_name: '',
- to_mail: email as string,
- subject: "Temp Mail Verify code",
- content: `Your verify code is ${code}`,
- is_html: false,
- })
- } catch (e) {
- return c.text(`Failed to send verify code: ${(e as Error).message}`, 500)
- }
- // save to KV
- await c.env.KV.put(`temp-mail:${email}`, code, { expirationTtl: 300 });
- return c.json({
- success: true,
- expirationTtl: 300
- })
- },
- register: async (c: Context) => {
- const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
- const settings = new UserSettings(value)
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- // check enable
- if (!settings.enable) {
- return c.text(msgs.UserRegistrationDisabledMsg, 403);
- }
- // check request
- const { email, password, code } = await c.req.json();
- if (!email || !password) {
- return c.text(msgs.InvalidEmailOrPasswordMsg, 400)
- }
- checkUserPassword(password);
- if (settings.enableMailVerify && !code) {
- return c.text(msgs.InvalidVerifyCodeMsg, 400)
- }
- // check mail domain allow list
- const mailDomain = email.split("@")[1];
- if (settings.enableMailAllowList
- && settings.mailAllowList
- && !settings.mailAllowList.includes(mailDomain)
- ) {
- return c.text(`${msgs.UserMailDomainMustInMsg} ${JSON.stringify(settings.mailAllowList, null, 2)}`, 400)
- }
- // check code
- if (settings.enableMailVerify) {
- const verifyCode = await c.env.KV.get(`temp-mail:${email}`)
- if (verifyCode != code) {
- return c.text(msgs.InvalidVerifyCodeMsg, 400)
- }
- }
- // geo data
- const reqIp = c.req.raw.headers.get("cf-connecting-ip")
- const geoData = new GeoData(reqIp, c.req.raw.cf as any);
- const userInfo = new UserInfo(geoData, email);
- // if not enable mail verify, do not on conflict update
- if (!settings.enableMailVerify) {
- try {
- const { success } = await c.env.DB.prepare(
- `INSERT INTO users (user_email, password, user_info)`
- + ` VALUES (?, ?, ?)`
- ).bind(
- email, password, JSON.stringify(userInfo)
- ).run();
- if (!success) {
- return c.text(msgs.FailedToRegisterMsg, 500)
- }
- } catch (e) {
- const error = e as Error;
- if (error.message && error.message.includes("UNIQUE")) {
- return c.text(msgs.UserAlreadyExistsMsg, 400)
- }
- return c.text(`${msgs.FailedToRegisterMsg}: ${error.message}`, 500)
- }
- return c.json({ success: true })
- }
- // if enable mail verify, on conflict update
- const { success } = await c.env.DB.prepare(
- `INSERT INTO users (user_email, password, user_info)`
- + ` VALUES (?, ?, ?)`
- + ` ON CONFLICT(user_email) DO UPDATE SET password = ?, user_info = ?, updated_at = datetime('now')`
- ).bind(
- email, password, JSON.stringify(userInfo),
- password, JSON.stringify(userInfo)
- ).run();
- if (!success) {
- return c.text(msgs.FailedToRegisterMsg, 400);
- }
- const defaultRole = getStringValue(c.env.USER_DEFAULT_ROLE);
- if (!defaultRole) return c.json({ success: true })
- const user_roles = getUserRoles(c);
- if (!user_roles.find((r) => r.role === defaultRole)) {
- return c.text(msgs.InvalidUserDefaultRoleMsg, 500);
- }
- // find user_id
- const user_id = await c.env.DB.prepare(
- `SELECT id FROM users where user_email = ?`
- ).bind(email).first("id");
- if (!user_id) {
- return c.text(msgs.UserNotFoundMsg, 500);
- }
- // update user roles
- const { success: success2 } = await c.env.DB.prepare(
- `INSERT INTO user_roles (user_id, role_text)`
- + ` VALUES (?, ?)`
- + ` ON CONFLICT(user_id) DO NOTHING`
- ).bind(user_id, defaultRole).run();
- if (!success2) {
- return c.text(msgs.FailedUpdateUserDefaultRoleMsg, 500);
- }
- return c.json({ success: true })
- },
- login: async (c: Context) => {
- const { email, password } = await c.req.json();
- const lang = c.get("lang") || c.env.DEFAULT_LANG;
- const msgs = i18n.getMessages(lang);
- if (!email || !password) return c.text(msgs.InvalidEmailOrPasswordMsg, 400);
- const { id: user_id, password: dbPassword } = await c.env.DB.prepare(
- `SELECT id, password FROM users where user_email = ?`
- ).bind(email).first() || {};
- if (!dbPassword) {
- return c.text(msgs.UserNotFoundMsg, 400)
- }
- // TODO: need check password use random salt
- if (dbPassword != password) {
- return c.text(msgs.InvalidEmailOrPasswordMsg, 400)
- }
- // create jwt
- const jwt = await Jwt.sign({
- user_email: email,
- user_id: user_id,
- // 90 days expire in seconds
- exp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
- iat: Math.floor(Date.now() / 1000),
- }, c.env.JWT_SECRET, "HS256")
- return c.json({
- jwt: jwt
- })
- },
-}
diff --git a/cloudflare_temp_email/worker/src/user_api/user_mail_api.ts b/cloudflare_temp_email/worker/src/user_api/user_mail_api.ts
deleted file mode 100644
index fd3534a..0000000
--- a/cloudflare_temp_email/worker/src/user_api/user_mail_api.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Context } from "hono";
-import { handleListQuery } from "../common";
-import UserBindAddressModule from "./bind_address";
-
-export default {
- getMails: async (c: Context) => {
- const { user_id } = c.get("userPayload");
- const { address, limit, offset, keyword } = c.req.query();
- const bindedAddressList = await UserBindAddressModule.getBindedAddressListById(c, user_id);
- const addressList = address ? bindedAddressList.filter((item) => item == address) : bindedAddressList;
- const addressQuery = `address IN (${addressList.map(() => "?").join(",")})`;
- const addressParams = addressList;
- const keywordQuery = keyword ? `raw like ?` : "";
- const keywordParams = keyword ? [`%${keyword}%`] : [];
-
- // user must have at least one binded address to query mails
- if (addressList.length <= 0) {
- return c.json({ results: [], count: 0 });
- }
-
- const filterQuerys = [addressQuery, keywordQuery].filter((item) => item).join(" and ");
- const finalQuery = filterQuerys.length > 0 ? `where ${filterQuerys}` : "";
- const filterParams = [...addressParams, ...keywordParams]
- return await handleListQuery(c,
- `SELECT * FROM raw_mails ${finalQuery}`,
- `SELECT count(*) as count FROM raw_mails ${finalQuery}`,
- filterParams, limit, offset
- );
- },
- deleteMail: async (c: Context) => {
- const { id } = c.req.param();
- const { user_id } = c.get("userPayload");
- const bindedAddressList = await UserBindAddressModule.getBindedAddressListById(c, user_id);
- const { success } = await c.env.DB.prepare(
- `DELETE FROM raw_mails WHERE id = ?`
- + ` and address IN (${bindedAddressList.map(() => "?").join(",")})`
- ).bind(id, ...bindedAddressList).run();
- return c.json({
- success: success
- })
- }
-}
diff --git a/cloudflare_temp_email/worker/src/utils.ts b/cloudflare_temp_email/worker/src/utils.ts
deleted file mode 100644
index 0dcfa7b..0000000
--- a/cloudflare_temp_email/worker/src/utils.ts
+++ /dev/null
@@ -1,321 +0,0 @@
-import { Context } from "hono";
-import { createMimeMessage } from "mimetext";
-
-export const getJsonObjectValue = (
- value: string | any
-): T | null => {
- if (value == undefined || value == null) {
- return null;
- }
- if (typeof value === "object") {
- return value as T;
- }
- if (typeof value !== "string") {
- return null;
- }
- try {
- return JSON.parse(value) as T;
- } catch (e) {
- console.error(`GetJsonValue: Failed to parse ${value}`, e);
- }
- return null;
-}
-
-export const getJsonSetting = async (
- c: Context, key: string
-): Promise => {
- const value = await getSetting(c, key);
- if (!value) {
- return null;
- }
- try {
- return JSON.parse(value) as T;
- } catch (e) {
- console.error(`GetJsonSetting: Failed to parse ${key}`, e);
- }
- return null;
-}
-
-export const getSetting = async (
- c: Context, key: string
-): Promise