mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-05-06 20:32:55 +08:00
Add AI-powered email content extraction feature using Cloudflare Workers AI to automatically identify and extract important information from emails including verification codes, authentication links, service links, and subscription links. Features: - AI extraction with priority-based logic (auth_code > auth_link > service_link > subscription_link > other_link) - Admin allowlist configuration with wildcard support (*@example.com) - Frontend display in both email list (compact) and detail view (full mode) - Bilingual documentation (Chinese/English) - Database migration: add metadata field to raw_mails (v0.0.3 -> v0.0.4) Technical highlights: - Proper regex escaping for wildcard pattern matching - Content truncation to avoid AI token limits - Error handling that won't affect email receiving - JSON schema validation for AI responses - Type-safe TypeScript implementation - Vue I18n support with special character escaping References: - Inspired by Alle Project: https://github.com/bestruirui/Alle - Uses Cloudflare Workers AI JSON Mode 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
180 lines
5.8 KiB
TypeScript
180 lines
5.8 KiB
TypeScript
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,
|
|
metadata TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_raw_mails_address ON raw_mails(address);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_raw_mails_created_at ON raw_mails(created_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS address (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT UNIQUE,
|
|
password TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_address_name ON address(name);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_address_created_at ON address(created_at);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_address_updated_at ON address(updated_at);
|
|
|
|
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 INDEX IF NOT EXISTS idx_sendbox_created_at ON sendbox(created_at);
|
|
|
|
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<HonoCustomType>) => {
|
|
// 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<HonoCustomType>) => {
|
|
const version = await utils.getSetting(c, CONSTANTS.DB_VERSION_KEY);
|
|
if (version == "v0.0.2") {
|
|
// example migration from v0.0.2 to v0.0.3
|
|
const query = `ALTER TABLE address ADD password TEXT;`
|
|
await c.env.DB.exec(query);
|
|
}
|
|
if (version == "v0.0.3") {
|
|
// migration from v0.0.3 to v0.0.4
|
|
await c.env.DB.exec(`ALTER TABLE raw_mails ADD COLUMN metadata TEXT;`);
|
|
}
|
|
if (version != CONSTANTS.DB_VERSION) {
|
|
// 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);
|
|
// 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<HonoCustomType>) => {
|
|
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
|
|
});
|
|
},
|
|
}
|