package users_service import ( "fmt" "html" "strings" ) // Письма содержат текстовую и HTML-версию; sender собирает multipart/alternative. const ( // welcomeSubject — тема письма о регистрации: конкретика без КАПСА и // кликбейта, до 50 символов (видно на мобильных целиком). welcomeSubject = "Твой доступ к «Вечернему детективу»" // resetSubject — тема письма о сбросе пароля. resetSubject = "Новый пароль для «Вечернего детектива»" ) // welcomePlain возвращает текстовую версию письма о регистрации. func welcomePlain(username, email, password, loginURL string) string { var b strings.Builder b.WriteString(greeting(username)) b.WriteString("\n\n") b.WriteString("Твоя регистрация в «Вечернем детективе» прошла. Данные для входа:\n\n") b.WriteString(" Логин: ") b.WriteString(email) b.WriteString("\n") b.WriteString(" Пароль: ") b.WriteString(password) b.WriteString("\n\n") if loginURL != "" { b.WriteString("Войти в игру: ") b.WriteString(loginURL) b.WriteString("\n\n") } b.WriteString("Если ты не регистрировался — просто проигнорируй это письмо.\n\n") b.WriteString("— Команда «Вечернего детектива»\n") b.WriteString("P.S. Сохрани это письмо: пароль понадобится для входа.") return sanitizeLine(b.String()) } // welcomeHTML возвращает HTML-версию письма о регистрации. func welcomeHTML(username, email, password, loginURL string) string { var content strings.Builder content.WriteString("

Привет, ") content.WriteString(html.EscapeString(username)) content.WriteString("!

\n") content.WriteString("

Твоя регистрация в «Вечернем детективе» прошла. Данные для входа:

\n") content.WriteString(credentialsBlockHTML(email, password)) if loginURL != "" { content.WriteString(buttonHTML(loginURL, "Войти в игру")) } content.WriteString("

Если ты не регистрировался — просто проигнорируй это письмо.

") return wrapHTML(content.String(), loginURL) } // resetPlain возвращает текстовую версию письма о сбросе пароля. func resetPlain(username, email, password, loginURL string) string { var b strings.Builder b.WriteString(greeting(username)) b.WriteString("\n\n") b.WriteString("Ты запросил сброс пароля в «Вечернем детективе». Новый пароль для входа:\n\n") b.WriteString(" Логин: ") b.WriteString(email) b.WriteString("\n") b.WriteString(" Пароль: ") b.WriteString(password) b.WriteString("\n\n") if loginURL != "" { b.WriteString("Войти в игру: ") b.WriteString(loginURL) b.WriteString("\n\n") } b.WriteString("Никому не сообщай пароль и не пересылай это письмо.\n") b.WriteString("Если это был не ты — ответь на это письмо: мы поможем защитить аккаунт.\n\n") b.WriteString("— Команда «Вечернего детектива»\n") return sanitizeLine(b.String()) } // resetHTML возвращает HTML-версию письма о сбросе пароля. func resetHTML(username, email, password, loginURL string) string { var content strings.Builder content.WriteString("

Привет, ") content.WriteString(html.EscapeString(username)) content.WriteString("!

\n") content.WriteString("

Ты запросил сброс пароля в «Вечернем детективе». Новый пароль для входа:

\n") content.WriteString(credentialsBlockHTML(email, password)) if loginURL != "" { content.WriteString(buttonHTML(loginURL, "Войти в игру")) } content.WriteString("

Никому не сообщай пароль и не пересылай это письмо.

\n") content.WriteString("

Если это был не ты — ответь на это письмо: мы поможем защитить аккаунт.

") return wrapHTML(content.String(), loginURL) } // greeting возвращает приветствие с именем пользователя; при пустом имени — // без обращения, чтобы не было «Привет, !». func greeting(username string) string { username = sanitizeLine(strings.TrimSpace(username)) if username == "" { return "Привет!" } return fmt.Sprintf("Привет, %s!", username) } // credentialsBlockHTML — блок «Логин / Пароль» в табличной вёрстке, // совместимой с Gmail. func credentialsBlockHTML(email, password string) string { return `
Логин
` + html.EscapeString(email) + `
Пароль
` + html.EscapeString(password) + `
` } // buttonHTML — кнопка входа с inline-стилями и табличным фоном. func buttonHTML(link, label string) string { return `
` + html.EscapeString(label) + `
` } // wrapHTML — каркас письма: тёмная шапка с названием сервиса, контент, // подвал. Основное оформление — inline; при блокировке текстур и шрифта // остаются светлый фон и системный шрифт. func wrapHTML(content, baseURL string) string { return `
ВЕЧЕРНИЙ ДЕТЕКТИВ
` + content + `
Это автоматическое письмо сервиса «Вечерний детектив».
Если ты получил его по ошибке — просто ответь на это письмо.
` } // Статические копии игровых текстур и шрифта находятся в public/email фронтенда. func emailAssetURL(baseURL, filename string) string { if baseURL == "" { return "" } return html.EscapeString(strings.TrimRight(baseURL, "/") + "/email/" + filename) } // sanitizeLine убирает переносы строк из пользовательского ввода, // вставляемого в тело письма (защита от инъекции строк в DATA-фазу SMTP). func sanitizeLine(s string) string { return strings.NewReplacer("\r", "", "\n", "").Replace(s) } // loginURL нормализует адрес фронтенда для ссылки в письме: срезает // завершающий слэш и допускает только http(s). Пустое или некорректное // значение — ссылка в письма не добавляется. func loginURL(baseURL string) string { baseURL = strings.TrimSpace(baseURL) baseURL = strings.TrimRight(baseURL, "/") if baseURL == "" { return "" } if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") { return "" } return baseURL }