feat: add var DISABLE_CUSTOM_ADDRESS_NAME and CREATE_ADDRESS_DEFAULT_… (#717)

* feat: add var DISABLE_CUSTOM_ADDRESS_NAME and CREATE_ADDRESS_DEFAULT_DOMAIN_FIRST

* fix: enhance input validation with trim() for address creation

- Add trim() handling in newAddress() function to prevent whitespace issues
- Add trim() handling for address prefixes to ensure consistent formatting
- Add trim() handling in Telegram API address parsing for robustness
- Prevents edge cases with whitespace-only or padded input strings

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Dream Hunter
2025-09-04 21:04:42 +08:00
committed by GitHub
co-authored by Claude
parent 648e9f7adf
commit 3fbace871c
14 changed files with 99 additions and 39 deletions
+2
View File
@@ -3,6 +3,8 @@
## main(v1.0.5) ## main(v1.0.5)
- feat: 新增 `DISABLE_CUSTOM_ADDRESS_NAME` 配置: 禁用自定义邮箱地址名称功能
- feat: 新增 `CREATE_ADDRESS_DEFAULT_DOMAIN_FIRST` 配置: 创建地址时优先使用第一个域名
- feat: |UI| 主页增加进入极简模式按钮 - feat: |UI| 主页增加进入极简模式按钮
## v1.0.4 ## v1.0.4
+2 -2
View File
@@ -1,5 +1,5 @@
<!-- markdownlint-disable-file MD033 MD045 --> <!-- markdownlint-disable-file MD033 MD045 -->
# 🚀 Cloudflare 临时邮箱 - 免费搭建临时邮件服务 # Cloudflare 临时邮箱 - 免费搭建临时邮件服务
<p align="center"> <p align="center">
<a href="https://temp-mail-docs.awsl.uk" target="_blank"> <a href="https://temp-mail-docs.awsl.uk" target="_blank">
@@ -81,7 +81,7 @@
<details open> <details open>
<summary>📖 目录(点击收缩/展开)</summary> <summary>📖 目录(点击收缩/展开)</summary>
- [🚀 Cloudflare 临时邮箱 - 免费搭建临时邮件服务](#-cloudflare-临时邮箱---免费搭建临时邮件服务) - [Cloudflare 临时邮箱 - 免费搭建临时邮件服务](#cloudflare-临时邮箱---免费搭建临时邮件服务)
- [📚 部署文档 - 快速开始](#-部署文档---快速开始) - [📚 部署文档 - 快速开始](#-部署文档---快速开始)
- [📝 更新日志](#-更新日志) - [📝 更新日志](#-更新日志)
- [🎯 在线体验](#-在线体验) - [🎯 在线体验](#-在线体验)
+1
View File
@@ -78,6 +78,7 @@ const getOpenSettings = async (message, notification) => {
adminContact: res["adminContact"] || "", adminContact: res["adminContact"] || "",
enableUserCreateEmail: res["enableUserCreateEmail"] || false, enableUserCreateEmail: res["enableUserCreateEmail"] || false,
disableAnonymousUserCreateEmail: res["disableAnonymousUserCreateEmail"] || false, disableAnonymousUserCreateEmail: res["disableAnonymousUserCreateEmail"] || false,
disableCustomAddressName: res["disableCustomAddressName"] || false,
enableUserDeleteEmail: res["enableUserDeleteEmail"] || false, enableUserDeleteEmail: res["enableUserDeleteEmail"] || false,
enableAutoReply: res["enableAutoReply"] || false, enableAutoReply: res["enableAutoReply"] || false,
enableIndexAbout: res["enableIndexAbout"] || false, enableIndexAbout: res["enableIndexAbout"] || false,
+1
View File
@@ -22,6 +22,7 @@ export const useGlobalState = createGlobalState(
adminContact: '', adminContact: '',
enableUserCreateEmail: false, enableUserCreateEmail: false,
disableAnonymousUserCreateEmail: false, disableAnonymousUserCreateEmail: false,
disableCustomAddressName: false,
enableUserDeleteEmail: false, enableUserDeleteEmail: false,
enableAutoReply: false, enableAutoReply: false,
enableIndexAbout: false, enableIndexAbout: false,
+10 -5
View File
@@ -84,6 +84,7 @@ const { locale, t } = useI18n({
credentialInput: 'Please input the Mail Address Credential', credentialInput: 'Please input the Mail Address Credential',
bindUserInfo: 'Logged in user, login without binding email or create new email address will bind to current user', bindUserInfo: 'Logged in user, login without binding email or create new email address will bind to current user',
bindUserAddressError: 'Error when bind email address to user', bindUserAddressError: 'Error when bind email address to user',
autoGeneratedName: 'Auto-generated name',
}, },
zh: { zh: {
login: '登录', login: '登录',
@@ -100,6 +101,7 @@ const { locale, t } = useI18n({
credentialInput: '请输入邮箱地址凭据', credentialInput: '请输入邮箱地址凭据',
bindUserInfo: '已登录用户, 登录未绑定邮箱或创建新邮箱地址将绑定到当前用户', bindUserInfo: '已登录用户, 登录未绑定邮箱或创建新邮箱地址将绑定到当前用户',
bindUserAddressError: '绑定邮箱地址到用户时错误', bindUserAddressError: '绑定邮箱地址到用户时错误',
autoGeneratedName: '自动生成名称',
} }
} }
}); });
@@ -147,8 +149,10 @@ const generateName = async () => {
const newEmail = async () => { const newEmail = async () => {
try { try {
// If custom names are disabled, send empty name to trigger backend auto-generation
const nameToSend = openSettings.value.disableCustomAddressName ? "" : emailName.value;
const res = await props.newAddressPath( const res = await props.newAddressPath(
emailName.value, nameToSend,
emailDomain.value, emailDomain.value,
cfToken.value cfToken.value
); );
@@ -240,19 +244,20 @@ onMounted(async () => {
<n-spin :show="generateNameLoading"> <n-spin :show="generateNameLoading">
<n-form> <n-form>
<span> <span>
<p>{{ t("getNewEmailTip1") + addressRegex.source }}</p> <p v-if="!openSettings.disableCustomAddressName">{{ t("getNewEmailTip1") + addressRegex.source }}</p>
<p>{{ t("getNewEmailTip2") }}</p> <p v-if="!openSettings.disableCustomAddressName">{{ t("getNewEmailTip2") }}</p>
<p>{{ t("getNewEmailTip3") }}</p> <p>{{ t("getNewEmailTip3") }}</p>
</span> </span>
<n-button @click="generateName" style="margin-bottom: 10px;"> <n-button v-if="!openSettings.disableCustomAddressName" @click="generateName" style="margin-bottom: 10px;">
{{ t('generateName') }} {{ t('generateName') }}
</n-button> </n-button>
<n-input-group> <n-input-group>
<n-input-group-label v-if="addressPrefix"> <n-input-group-label v-if="addressPrefix">
{{ addressPrefix }} {{ addressPrefix }}
</n-input-group-label> </n-input-group-label>
<n-input v-model:value="emailName" show-count :minlength="openSettings.minAddressLen" <n-input v-if="!openSettings.disableCustomAddressName" v-model:value="emailName" show-count :minlength="openSettings.minAddressLen"
:maxlength="openSettings.maxAddressLen" /> :maxlength="openSettings.maxAddressLen" />
<n-input v-else :value="t('autoGeneratedName')" disabled />
<n-input-group-label>@</n-input-group-label> <n-input-group-label>@</n-input-group-label>
<n-select v-model:value="emailDomain" :consistent-menu-width="false" <n-select v-model:value="emailDomain" :consistent-menu-width="false"
:options="domainsOptions" /> :options="domainsOptions" />
@@ -24,9 +24,9 @@ res = requests.post(
print(res.json()) print(res.json())
``` ```
# 批量创建随机用户名邮箱地址 API 示例 ## 批量创建随机用户名邮箱地址 API 示例
## 通过 admin API 批量新建邮箱地址 ### 通过 admin API 批量新建邮箱地址
这是一个 `python` 的例子,使用 `requests` 库发送邮件。 这是一个 `python` 的例子,使用 `requests` 库发送邮件。
+3 -1
View File
@@ -23,13 +23,15 @@
## 邮箱相关变量 ## 邮箱相关变量
| 变量名 | 类型 | 说明 | 示例 | | 变量名 | 类型 | 说明 | 示例 |
| ---------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | | ------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| `PREFIX` | 文本 | 新建 `邮箱名称` 的默认前缀,不需要前缀可不配置 | `tmp` | | `PREFIX` | 文本 | 新建 `邮箱名称` 的默认前缀,不需要前缀可不配置 | `tmp` |
| `MIN_ADDRESS_LEN` | 数字 | `邮箱名称` 的最小长度 | `1` | | `MIN_ADDRESS_LEN` | 数字 | `邮箱名称` 的最小长度 | `1` |
| `MAX_ADDRESS_LEN` | 数字 | `邮箱名称` 的最大长度 | `30` | | `MAX_ADDRESS_LEN` | 数字 | `邮箱名称` 的最大长度 | `30` |
| `DISABLE_CUSTOM_ADDRESS_NAME` | 文本/JSON | 禁用自定义邮箱地址名称,如果设置为 true,则用户无法输入自定义邮箱名称,将由后台自动生成 | `true` |
| `ADDRESS_CHECK_REGEX` | 文本 | `邮箱名称` 的正则表达式, 只用于检查 | `^(?!.*admin).*` | | `ADDRESS_CHECK_REGEX` | 文本 | `邮箱名称` 的正则表达式, 只用于检查 | `^(?!.*admin).*` |
| `ADDRESS_REGEX` | 文本 | `邮箱名称` 替换非法符号的正则表达式, 不在其中的符号将被替换,如果不设置,默认为 `[^a-z0-9]`, 需谨慎使用, 有些符号可能导致无法收件 | `[^a-z0-9]` | | `ADDRESS_REGEX` | 文本 | `邮箱名称` 替换非法符号的正则表达式, 不在其中的符号将被替换,如果不设置,默认为 `[^a-z0-9]`, 需谨慎使用, 有些符号可能导致无法收件 | `[^a-z0-9]` |
| `DEFAULT_DOMAINS` | JSON | 默认用户可用的域名(未登录或未分配角色的用户) | `["awsl.uk", "dreamhunter2333.xyz"]` | | `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"]` | | `DOMAIN_LABELS` | JSON | 对于中文域名,可以使用 DOMAIN_LABELS 显示域名的中文展示名称 | `["中文.awsl.uk", "dreamhunter2333.xyz"]` |
| `ENABLE_AUTO_REPLY` | 文本/JSON | 允许自动回复邮件 | `true` | | `ENABLE_AUTO_REPLY` | 文本/JSON | 允许自动回复邮件 | `true` |
| `DEFAULT_SEND_BALANCE` | 文本/JSON | 默认发送邮件余额,如果不设置,将为 0 | `1` | | `DEFAULT_SEND_BALANCE` | 文本/JSON | 默认发送邮件余额,如果不设置,将为 0 | `1` |
+2
View File
@@ -14,6 +14,7 @@ api.get('/open_api/settings', async (c) => {
const auth = c.req.raw.headers.get("x-custom-auth"); const auth = c.req.raw.headers.get("x-custom-auth");
needAuth = !auth || !passwords.includes(auth); needAuth = !auth || !passwords.includes(auth);
} }
return c.json({ return c.json({
"title": c.env.TITLE, "title": c.env.TITLE,
"announcement": utils.getStringValue(c.env.ANNOUNCEMENT), "announcement": utils.getStringValue(c.env.ANNOUNCEMENT),
@@ -29,6 +30,7 @@ api.get('/open_api/settings', async (c) => {
"adminContact": c.env.ADMIN_CONTACT, "adminContact": c.env.ADMIN_CONTACT,
"enableUserCreateEmail": utils.getBooleanValue(c.env.ENABLE_USER_CREATE_EMAIL), "enableUserCreateEmail": utils.getBooleanValue(c.env.ENABLE_USER_CREATE_EMAIL),
"disableAnonymousUserCreateEmail": utils.getBooleanValue(c.env.DISABLE_ANONYMOUS_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), "enableUserDeleteEmail": utils.getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL),
"enableAutoReply": utils.getBooleanValue(c.env.ENABLE_AUTO_REPLY), "enableAutoReply": utils.getBooleanValue(c.env.ENABLE_AUTO_REPLY),
"enableIndexAbout": utils.getBooleanValue(c.env.ENABLE_INDEX_ABOUT), "enableIndexAbout": utils.getBooleanValue(c.env.ENABLE_INDEX_ABOUT),
+35 -5
View File
@@ -8,6 +8,31 @@ import { AdminWebhookSettings, WebhookMail, WebhookSettings } from './models';
const DEFAULT_NAME_REGEX = /[^a-z0-9]/g; const DEFAULT_NAME_REGEX = /[^a-z0-9]/g;
export const generateRandomName = (c: Context<HonoCustomType>): 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<HonoCustomType>, name: string) => { const checkNameRegex = (c: Context<HonoCustomType>, name: string) => {
let error = null; let error = null;
try { try {
@@ -76,8 +101,8 @@ export const newAddress = async (
enableCheckNameRegex?: boolean, enableCheckNameRegex?: boolean,
} }
): Promise<{ address: string, jwt: string }> => { ): Promise<{ address: string, jwt: string }> => {
// remove special characters // trim whitespace and remove special characters
name = name.replace(getNameRegex(c), '') name = name.trim().replace(getNameRegex(c), '')
// check name // check name
if (enableCheckNameRegex) { if (enableCheckNameRegex) {
await checkNameBlockList(c, name); await checkNameBlockList(c, name);
@@ -102,16 +127,21 @@ export const newAddress = async (
} }
// create address with prefix // create address with prefix
if (typeof addressPrefix === "string") { if (typeof addressPrefix === "string") {
name = addressPrefix + name; name = addressPrefix.trim() + name;
} else if (enablePrefix) { } else if (enablePrefix) {
name = getStringValue(c.env.PREFIX) + name; name = getStringValue(c.env.PREFIX).trim() + name;
} }
// check domain // check domain
const allowDomains = checkAllowDomains ? await getAllowDomains(c) : getDomains(c); const allowDomains = checkAllowDomains ? await getAllowDomains(c) : getDomains(c);
// if domain is not set, use the random domain // if domain is not set, select domain based on environment configuration
if (!domain && allowDomains.length > 0) { 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)]; domain = allowDomains[Math.floor(Math.random() * allowDomains.length)];
} }
}
// check domain is valid // check domain is valid
if (!domain || !allowDomains.includes(domain)) { if (!domain || !allowDomains.includes(domain)) {
throw new Error("Invalid domain") throw new Error("Invalid domain")
+8 -4
View File
@@ -2,7 +2,7 @@ import { Context, Hono } from 'hono'
import i18n from '../i18n'; import i18n from '../i18n';
import { getBooleanValue, getJsonSetting, checkCfTurnstile, getStringValue, getSplitStringListValue } from '../utils'; import { getBooleanValue, getJsonSetting, checkCfTurnstile, getStringValue, getSplitStringListValue } from '../utils';
import { newAddress, handleListQuery, deleteAddressWithData, getAddressPrefix, getAllowDomains, updateAddressUpdatedAt } from '../common' import { newAddress, handleListQuery, deleteAddressWithData, getAddressPrefix, getAllowDomains, updateAddressUpdatedAt, generateRandomName } from '../common'
import { CONSTANTS } from '../constants' import { CONSTANTS } from '../constants'
import auto_reply from './auto_reply' import auto_reply from './auto_reply'
import webhook_settings from './webhook_settings'; import webhook_settings from './webhook_settings';
@@ -123,9 +123,13 @@ api.post('/api/new_address', async (c) => {
} catch (error) { } catch (error) {
return c.text(msgs.TurnstileCheckFailedMsg, 500) return c.text(msgs.TurnstileCheckFailedMsg, 500)
} }
// if no name, generate random name // Check if custom email names are disabled from environment variable
if (!name) { const disableCustomAddressName = getBooleanValue(c.env.DISABLE_CUSTOM_ADDRESS_NAME);
name = Math.random().toString(36).substring(2, 15);
// 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 // check name block list
try { try {
+15 -8
View File
@@ -1,8 +1,8 @@
import { Context } from "hono"; import { Context } from "hono";
import { Jwt } from "hono/utils/jwt"; import { Jwt } from "hono/utils/jwt";
import { CONSTANTS } from "../constants"; import { CONSTANTS } from "../constants";
import { getIntValue, getJsonSetting } from "../utils"; import { getBooleanValue, getIntValue, getJsonSetting } from "../utils";
import { deleteAddressWithData, newAddress } from "../common"; import { deleteAddressWithData, newAddress, generateRandomName } from "../common";
export const tgUserNewAddress = async ( export const tgUserNewAddress = async (
c: Context<HonoCustomType>, userId: string, address: string c: Context<HonoCustomType>, userId: string, address: string
@@ -15,21 +15,28 @@ export const tgUserNewAddress = async (
throw Error("Rate limit exceeded") throw Error("Rate limit exceeded")
} }
} }
// @ts-ignore // Check if custom address names are disabled
address = address || Math.random().toString(36).substring(2, 15); const disableCustomAddressName = getBooleanValue(c.env.DISABLE_CUSTOM_ADDRESS_NAME);
const [name, domain] = address.includes("@") ? address.split("@") : [address, null];
// 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<string[]>(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || []; const jwtList = await c.env.KV.get<string[]>(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
if (jwtList.length >= getIntValue(c.env.TG_MAX_ADDRESS, 5)) { if (jwtList.length >= getIntValue(c.env.TG_MAX_ADDRESS, 5)) {
throw Error("绑定地址数量已达上限"); throw Error("绑定地址数量已达上限");
} }
// Generate name if disabled or not provided
const finalName = (!name || disableCustomAddressName) ? generateRandomName(c) : name;
// check name block list // check name block list
const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY); const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
const blockList = (value || []) as string[]; const blockList = (value || []) as string[];
if (blockList.some((item) => name.includes(item))) { if (blockList.some((item) => finalName.includes(item))) {
throw Error(`Name[${name}]is blocked`); throw Error(`Name[${finalName}]is blocked`);
} }
const res = await newAddress(c, { const res = await newAddress(c, {
name: name || Math.random().toString(36).substring(2, 15), name: finalName,
domain, domain,
enablePrefix: true enablePrefix: true
}); });
+1 -1
View File
@@ -18,7 +18,7 @@ const COMMANDS = [
}, },
{ {
command: "new", command: "new",
description: "新建邮箱地址, 如果要自定义邮箱地址, 请输入 /new <name>@<domain>, name [a-z0-9] 有效" description: "新建邮箱地址, 如果要自定义邮箱地址, 请输入 /new, 通过 /new <name>@<domain> 可以指定, name [a-z0-9] 有效, name 为空则随机生成, @<domain> 可选"
}, },
{ {
command: "address", command: "address",
+2
View File
@@ -24,6 +24,8 @@ type Bindings = {
MAX_ADDRESS_LEN: string | number | undefined MAX_ADDRESS_LEN: string | number | undefined
DEFAULT_DOMAINS: string | string[] | undefined DEFAULT_DOMAINS: string | string[] | undefined
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 ADMIN_USER_ROLE: string | undefined
USER_DEFAULT_ROLE: string | UserRole | undefined USER_DEFAULT_ROLE: string | UserRole | undefined
USER_ROLES: string | UserRole[] | undefined USER_ROLES: string | UserRole[] | undefined
+4
View File
@@ -35,6 +35,8 @@ PREFIX = "tmp"
# (min, max) length of the adderss, if not set, the default is (1, 30) # (min, max) length of the adderss, if not set, the default is (1, 30)
# MIN_ADDRESS_LEN = 1 # MIN_ADDRESS_LEN = 1
# MAX_ADDRESS_LEN = 30 # MAX_ADDRESS_LEN = 30
# Disable custom email address name, if set true, users cannot input custom email name, will auto generate
# DISABLE_CUSTOM_ADDRESS_NAME = true
# IF YOU WANT TO MAKE YOUR SITE PRIVATE, UNCOMMENT THE FOLLOWING LINES # IF YOU WANT TO MAKE YOUR SITE PRIVATE, UNCOMMENT THE FOLLOWING LINES
# PASSWORDS = ["123", "456"] # PASSWORDS = ["123", "456"]
# For admin panel # For admin panel
@@ -43,6 +45,8 @@ PREFIX = "tmp"
# DISABLE_ADMIN_PASSWORD_CHECK = false # DISABLE_ADMIN_PASSWORD_CHECK = false
# ADMIN CONTACT, CAN BE ANY STRING # ADMIN CONTACT, CAN BE ANY STRING
# ADMIN_CONTACT = "xx@xx.xxx" # ADMIN_CONTACT = "xx@xx.xxx"
# Create new address with default domain first, if set true, will use first domain from DEFAULT_DOMAINS when no domain specified
# CREATE_ADDRESS_DEFAULT_DOMAIN_FIRST = false
DEFAULT_DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # domain name for no role users DEFAULT_DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # domain name for no role users
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # all domain names DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # all domain names
# For chinese domain name, you can use DOMAIN_LABELS to show chinese domain name # For chinese domain name, you can use DOMAIN_LABELS to show chinese domain name