@@ -0,0 +1,641 @@
|
||||
package screens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
"github.com/TelegramExchange/tgex-backend/tg_bot/backend"
|
||||
"github.com/TelegramExchange/tgex-backend/tg_bot/bot"
|
||||
"github.com/TelegramExchange/tgex-backend/tg_bot/screens/ui"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type PurchaseChannelInput struct {
|
||||
ChannelID string
|
||||
Username string
|
||||
Title string
|
||||
InviteLink string
|
||||
PlannedCost *float64
|
||||
Comment *string
|
||||
}
|
||||
|
||||
type SelectChannelsForPurchase struct {
|
||||
ProjectID string
|
||||
ProjectTitle string
|
||||
ProjectTelegramID int64
|
||||
ProjectUsername string
|
||||
ProjectStatus string
|
||||
ProjectDefaultLinkType string
|
||||
CreativeID string
|
||||
CreativeTitle string
|
||||
Channels []PurchaseChannelInput
|
||||
CurrentPage int
|
||||
InvalidUsernames []string
|
||||
Duplicates []string // дубликаты каналов
|
||||
ParsingErrors []ParseError // ошибки парсинга с предложениями
|
||||
OptionalDetailsState *PurchaseOptionalDetails // сохранённое состояние деталей
|
||||
BackState bot.State
|
||||
}
|
||||
|
||||
// ParseError представляет ошибку парсинга с предложением исправления
|
||||
type ParseError struct {
|
||||
Input string
|
||||
Suggestion string
|
||||
}
|
||||
|
||||
func (s *SelectChannelsForPurchase) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
s.renderChannelSelection(b, mode)
|
||||
}
|
||||
|
||||
func (s *SelectChannelsForPurchase) renderChannelSelection(b *bot.Bot, mode bot.RenderMode) {
|
||||
text := "<b>Создание закупа</b>\n\n"
|
||||
text += "<b>Шаг 2/2:</b> Добавление каналов\n\n"
|
||||
|
||||
var buttons [][]echotron.InlineKeyboardButton
|
||||
|
||||
if len(s.Channels) == 0 {
|
||||
text += `Добавьте каналы для размещения рекламы
|
||||
|
||||
Отправьте username (без @) или invite link приватного канала
|
||||
Например: <code>channel_name</code> или <code>https://t.me/+abcdef</code>
|
||||
|
||||
После добавления всех каналов нажмите <b>Далее</b>`
|
||||
} else {
|
||||
text += fmt.Sprintf("Добавлено каналов: <b>%d</b>\n\n", len(s.Channels))
|
||||
|
||||
for i, ch := range s.Channels {
|
||||
channelText := fmt.Sprintf(" %d. %s", i+1, channelLabel(ch))
|
||||
if ch.PlannedCost != nil {
|
||||
channelText += fmt.Sprintf(" — %.0f₽", *ch.PlannedCost)
|
||||
}
|
||||
text += channelText + "\n"
|
||||
}
|
||||
text += "\n"
|
||||
|
||||
const channelsPerPage = 6
|
||||
if s.CurrentPage*channelsPerPage >= len(s.Channels) {
|
||||
s.CurrentPage = 0
|
||||
}
|
||||
start, end := ui.GetPageBounds(s.CurrentPage, channelsPerPage, len(s.Channels))
|
||||
|
||||
var slots []echotron.InlineKeyboardButton
|
||||
for i := start; i < end; i++ {
|
||||
ch := s.Channels[i]
|
||||
buttonText := channelLabel(ch)
|
||||
if ch.PlannedCost != nil {
|
||||
buttonText = fmt.Sprintf("%s — %.0f₽", channelLabel(ch), *ch.PlannedCost)
|
||||
}
|
||||
slots = append(slots, Button("✖ "+buttonText, fmt.Sprintf("remove_channel:%d", i)))
|
||||
}
|
||||
|
||||
if len(s.Channels) > channelsPerPage {
|
||||
for len(slots) < channelsPerPage {
|
||||
slots = append(slots, Button(" ", "empty"))
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(slots); i += 2 {
|
||||
row := []echotron.InlineKeyboardButton{slots[i]}
|
||||
if i+1 < len(slots) {
|
||||
row = append(row, slots[i+1])
|
||||
} else {
|
||||
row = append(row, Button(" ", "empty"))
|
||||
}
|
||||
buttons = append(buttons, row)
|
||||
}
|
||||
|
||||
pages := ui.CalculatePages(len(s.Channels), channelsPerPage)
|
||||
navRow := ui.BuildNavigationRow(ui.PaginationConfig{
|
||||
CurrentPage: s.CurrentPage,
|
||||
TotalPages: pages,
|
||||
})
|
||||
if navRow != nil {
|
||||
buttons = append(buttons, navRow)
|
||||
}
|
||||
|
||||
text += "\nДобавьте еще каналы или создайте закуп"
|
||||
|
||||
// Кнопка создания закупа (доступна только если есть каналы)
|
||||
}
|
||||
|
||||
if len(s.InvalidUsernames) > 0 {
|
||||
text += fmt.Sprintf("\n\n⚠️ Пропущены: %s", strings.Join(s.InvalidUsernames, ", "))
|
||||
s.InvalidUsernames = nil
|
||||
}
|
||||
|
||||
// Показываем дубликаты
|
||||
if len(s.Duplicates) > 0 {
|
||||
text += fmt.Sprintf("\n\n⏭️ Пропущены (дубликаты): %s", strings.Join(s.Duplicates, ", "))
|
||||
s.Duplicates = nil
|
||||
}
|
||||
|
||||
// Показываем ошибки с предложениями
|
||||
if len(s.ParsingErrors) > 0 {
|
||||
text += "\n\n❌ Ошибки форматирования:\n"
|
||||
for i, err := range s.ParsingErrors {
|
||||
if i < 3 { // Показываем максимум 3 ошибки
|
||||
if err.Suggestion != "" {
|
||||
text += fmt.Sprintf("• %s → возможно: %s\n", err.Input, err.Suggestion)
|
||||
} else {
|
||||
text += fmt.Sprintf("• %s\n", err.Input)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(s.ParsingErrors) > 3 {
|
||||
text += fmt.Sprintf("• ... и еще %d\n", len(s.ParsingErrors)-3)
|
||||
}
|
||||
s.ParsingErrors = nil
|
||||
}
|
||||
|
||||
// Кнопки навигации
|
||||
if len(s.Channels) > 0 {
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "back"),
|
||||
Button("→ Далее", "next_step"),
|
||||
))
|
||||
} else {
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "back"),
|
||||
))
|
||||
}
|
||||
|
||||
keyboard := Keyboard(buttons...)
|
||||
b.Render(text, keyboard, mode)
|
||||
}
|
||||
|
||||
func (s *SelectChannelsForPurchase) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
if u.CallbackQuery == nil || u.CallbackQuery.Data == "" {
|
||||
return
|
||||
}
|
||||
|
||||
data := u.CallbackQuery.Data
|
||||
|
||||
switch data {
|
||||
case "back":
|
||||
// Возвращаемся к выбору креатива
|
||||
b.SetState(&AddPurchase{
|
||||
ProjectID: s.ProjectID,
|
||||
ProjectTitle: s.ProjectTitle,
|
||||
ProjectTelegramID: s.ProjectTelegramID,
|
||||
ProjectUsername: s.ProjectUsername,
|
||||
ProjectStatus: s.ProjectStatus,
|
||||
CreativeID: s.CreativeID,
|
||||
CreativeTitle: s.CreativeTitle,
|
||||
ActivePicker: "",
|
||||
BackState: s.BackState,
|
||||
}, bot.EditMessage)
|
||||
|
||||
case "next_step":
|
||||
if len(s.Channels) == 0 {
|
||||
b.Edit("❌ Добавьте хотя бы один канал", Keyboard(
|
||||
Row(Button("← Назад", "back_to_select")),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем, что все каналы резолвлены (имеют ChannelID)
|
||||
var unresolved []string
|
||||
for _, ch := range s.Channels {
|
||||
if ch.ChannelID == "" {
|
||||
label := channelLabel(ch)
|
||||
unresolved = append(unresolved, label)
|
||||
}
|
||||
}
|
||||
|
||||
if len(unresolved) > 0 {
|
||||
text := "❌ Некоторые каналы не были найдены:\n\n"
|
||||
for _, label := range unresolved {
|
||||
text += fmt.Sprintf("• %s\n", label)
|
||||
}
|
||||
text += "\nУдалите их из списка и попробуйте снова"
|
||||
|
||||
b.Edit(text, Keyboard(
|
||||
Row(Button("← Назад", "back_to_select")),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
// Каналы уже резолвлены в HandleMessage, переходим к следующему шагу
|
||||
// Если есть сохранённое состояние - используем его, иначе создаём новое
|
||||
var optionalDetails *PurchaseOptionalDetails
|
||||
if s.OptionalDetailsState != nil {
|
||||
optionalDetails = s.OptionalDetailsState
|
||||
// Обновляем каналы на случай если они изменились
|
||||
optionalDetails.Channels = s.Channels
|
||||
} else {
|
||||
optionalDetails = &PurchaseOptionalDetails{
|
||||
ProjectID: s.ProjectID,
|
||||
ProjectTitle: s.ProjectTitle,
|
||||
ProjectDefaultLinkType: s.ProjectDefaultLinkType,
|
||||
CreativeID: s.CreativeID,
|
||||
CreativeTitle: s.CreativeTitle,
|
||||
Channels: s.Channels,
|
||||
BackState: s,
|
||||
}
|
||||
}
|
||||
b.SetState(optionalDetails, bot.EditMessage)
|
||||
|
||||
case "back_to_select":
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
case "prev":
|
||||
if s.CurrentPage > 0 {
|
||||
s.CurrentPage--
|
||||
}
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
case "next":
|
||||
s.CurrentPage++
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
default:
|
||||
if len(data) > 15 && data[:15] == "remove_channel:" {
|
||||
var index int
|
||||
if _, err := fmt.Sscanf(data[15:], "%d", &index); err == nil {
|
||||
if index >= 0 && index < len(s.Channels) {
|
||||
s.Channels = append(s.Channels[:index], s.Channels[index+1:]...)
|
||||
if s.CurrentPage > 0 {
|
||||
channelsPerPage := 6
|
||||
pages := ui.CalculatePages(len(s.Channels), channelsPerPage)
|
||||
if s.CurrentPage >= pages {
|
||||
s.CurrentPage = pages - 1
|
||||
}
|
||||
}
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
s.Enter(b, bot.NewMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SelectChannelsForPurchase) createPurchase(b *bot.Bot, jwt string) {
|
||||
// Преобразуем каналы в формат API
|
||||
var apiChannels []backend.CreatePlacementChannelInput
|
||||
for _, ch := range s.Channels {
|
||||
var details *backend.PlacementDetails
|
||||
if ch.PlannedCost != nil {
|
||||
cost := backend.CostInfo{
|
||||
Type: "fixed",
|
||||
Value: *ch.PlannedCost,
|
||||
}
|
||||
details = &backend.PlacementDetails{
|
||||
Cost: &cost,
|
||||
}
|
||||
}
|
||||
apiChannels = append(apiChannels, backend.CreatePlacementChannelInput{
|
||||
ChannelID: ch.ChannelID,
|
||||
Comment: ch.Comment,
|
||||
Details: details,
|
||||
})
|
||||
}
|
||||
|
||||
input := backend.CreatePlacementsInput{
|
||||
CreativeID: &s.CreativeID,
|
||||
Channels: apiChannels,
|
||||
}
|
||||
|
||||
placements, err := b.Backend.CreatePlacements(context.Background(), jwt, b.Session.WorkspaceID, s.ProjectID, input)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create placements")
|
||||
b.Edit("❌ Не удалось создать размещения\n\nВозможные причины:\n• Один из каналов не найден\n• Ошибка сервера", Keyboard(
|
||||
Row(Button("← Назад", "back_to_select")),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
if len(placements.Placements) == 0 {
|
||||
b.Edit("❌ Размещения не созданы", Keyboard(
|
||||
Row(Button("← Назад", "back_to_select")),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
b.SetState(&PlacementDetails{
|
||||
ProjectID: s.ProjectID,
|
||||
PlacementID: placements.Placements[0].ID,
|
||||
BackState: s.BackState,
|
||||
}, bot.EditMessage)
|
||||
}
|
||||
|
||||
func (s *SelectChannelsForPurchase) HandleMessage(b *bot.Bot, u *echotron.Update) {
|
||||
if u.Message == nil || u.Message.Text == "" {
|
||||
return
|
||||
}
|
||||
|
||||
raw := strings.TrimSpace(u.Message.Text)
|
||||
if raw == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем JWT
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
log.Error().Msg("JWT is empty in session")
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
// Разбиваем ввод на токены
|
||||
tokens := strings.FieldsFunc(raw, func(r rune) bool {
|
||||
return r == ' ' || r == '\n' || r == '\t' || r == ',' || r == ';'
|
||||
})
|
||||
|
||||
added := 0
|
||||
s.Duplicates = nil
|
||||
s.InvalidUsernames = nil
|
||||
s.ParsingErrors = nil
|
||||
|
||||
tokensLoop:
|
||||
for _, token := range tokens {
|
||||
entry := strings.TrimSpace(token)
|
||||
entry = strings.Trim(entry, ",;")
|
||||
if entry == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Используем новый парсер
|
||||
parsed := ParseChannelInput(entry)
|
||||
|
||||
if !parsed.Valid {
|
||||
// Пробуем предложить исправление
|
||||
if suggestion, ok := SuggestFix(entry); ok {
|
||||
s.ParsingErrors = append(s.ParsingErrors, ParseError{
|
||||
Input: entry,
|
||||
Suggestion: suggestion,
|
||||
})
|
||||
} else {
|
||||
s.InvalidUsernames = append(s.InvalidUsernames, entry)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Проверяем дубликаты по username/invite link
|
||||
if IsDuplicate(parsed, s.Channels) {
|
||||
label := FormatChannelLabel(parsed)
|
||||
s.Duplicates = append(s.Duplicates, label)
|
||||
continue
|
||||
}
|
||||
|
||||
// Сразу резолвим канал через бэкенд
|
||||
channel, err := s.resolveSingleChannel(b, jwt, parsed)
|
||||
if err != nil {
|
||||
// Канал не найден или ошибка
|
||||
s.InvalidUsernames = append(s.InvalidUsernames, entry)
|
||||
log.Error().Err(err).Str("input", entry).Msg("Failed to resolve channel")
|
||||
continue
|
||||
}
|
||||
|
||||
// Проверяем дубликаты по ID канала (после резолва)
|
||||
for _, ch := range s.Channels {
|
||||
if ch.ChannelID != "" && ch.ChannelID == channel.ID {
|
||||
var title, username string
|
||||
if channel.Title != nil {
|
||||
title = *channel.Title
|
||||
}
|
||||
if channel.Username != nil {
|
||||
username = *channel.Username
|
||||
}
|
||||
label := channelLabelByID(&channel.ID, &title, &username)
|
||||
s.Duplicates = append(s.Duplicates, label)
|
||||
continue tokensLoop // <-- Выход из внешнего цикла, а не внутреннего!
|
||||
}
|
||||
|
||||
// Дополнительная проверка по username (case-insensitive)
|
||||
// На случай, если канал еще не резолвлен или ID отличается
|
||||
if channel.Username != nil && ch.Username != "" {
|
||||
if strings.EqualFold(ch.Username, *channel.Username) {
|
||||
var title string
|
||||
if channel.Title != nil {
|
||||
title = *channel.Title
|
||||
}
|
||||
label := channelLabelByID(&channel.ID, &title, channel.Username)
|
||||
s.Duplicates = append(s.Duplicates, label)
|
||||
continue tokensLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем канал с полными данными
|
||||
var title string
|
||||
if channel.Title != nil {
|
||||
title = *channel.Title
|
||||
}
|
||||
s.Channels = append(s.Channels, PurchaseChannelInput{
|
||||
ChannelID: channel.ID,
|
||||
Username: parsed.Username,
|
||||
Title: title,
|
||||
InviteLink: parsed.InviteLink,
|
||||
})
|
||||
added++
|
||||
}
|
||||
|
||||
// Если ничего не добавлено и нет ошибок - не обновляем экран
|
||||
if added == 0 && len(s.InvalidUsernames) == 0 && len(s.Duplicates) == 0 && len(s.ParsingErrors) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Обновляем экран
|
||||
s.Enter(b, bot.NewMessage)
|
||||
}
|
||||
|
||||
// resolveSingleChannel резолвит один канал через бэкенд
|
||||
func (s *SelectChannelsForPurchase) resolveSingleChannel(b *bot.Bot, jwt string, parsed ChannelInput) (*backend.Channel, error) {
|
||||
var input backend.CreateChannelInput
|
||||
if parsed.InviteLink != "" {
|
||||
link := parsed.InviteLink
|
||||
input = backend.CreateChannelInput{InviteLink: &link}
|
||||
} else {
|
||||
username := parsed.Username
|
||||
input = backend.CreateChannelInput{Username: &username}
|
||||
}
|
||||
|
||||
resp, err := b.Backend.CreateChannels(context.Background(), jwt, backend.CreateChannelsInput{
|
||||
Channels: []backend.CreateChannelInput{input},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(resp.Results) == 0 {
|
||||
return nil, fmt.Errorf("no response from backend")
|
||||
}
|
||||
|
||||
result := resp.Results[0]
|
||||
if result.Status == "failed" || result.Channel == nil || result.Channel.ID == "" {
|
||||
errMsg := "channel not found"
|
||||
if result.Error != nil {
|
||||
errMsg = *result.Error
|
||||
}
|
||||
return nil, fmt.Errorf(errMsg)
|
||||
}
|
||||
|
||||
// Если пришел username из ответа, обновляем его
|
||||
if result.Channel.Username != nil && *result.Channel.Username != "" {
|
||||
parsed.Username = *result.Channel.Username
|
||||
}
|
||||
|
||||
return result.Channel, nil
|
||||
}
|
||||
|
||||
// channelLabelByID формирует метку канала по ID
|
||||
func channelLabelByID(id, title, username *string) string {
|
||||
if title != nil && *title != "" {
|
||||
return *title
|
||||
}
|
||||
if username != nil && *username != "" {
|
||||
return "@" + *username
|
||||
}
|
||||
if id != nil && *id != "" {
|
||||
return *id
|
||||
}
|
||||
return "канал"
|
||||
}
|
||||
|
||||
func (s *SelectChannelsForPurchase) resolveChannels(b *bot.Bot, jwt string) []string {
|
||||
inputs := make([]backend.CreateChannelInput, 0, len(s.Channels))
|
||||
for _, ch := range s.Channels {
|
||||
if ch.InviteLink != "" {
|
||||
link := ch.InviteLink
|
||||
inputs = append(inputs, backend.CreateChannelInput{InviteLink: &link})
|
||||
} else {
|
||||
username := ch.Username
|
||||
inputs = append(inputs, backend.CreateChannelInput{Username: &username})
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := b.Backend.CreateChannels(context.Background(), jwt, backend.CreateChannelsInput{
|
||||
Channels: inputs,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create channels")
|
||||
return []string{"ошибка сервера"}
|
||||
}
|
||||
|
||||
var failed []string
|
||||
for _, result := range resp.Results {
|
||||
if result.Status == "failed" || result.Channel == nil || result.Channel.ID == "" {
|
||||
if result.Index >= 0 && result.Index < len(s.Channels) {
|
||||
failed = append(failed, channelLabel(s.Channels[result.Index]))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if result.Index < 0 || result.Index >= len(s.Channels) {
|
||||
continue
|
||||
}
|
||||
ch := &s.Channels[result.Index]
|
||||
ch.ChannelID = result.Channel.ID
|
||||
if result.Channel.Username != nil {
|
||||
ch.Username = *result.Channel.Username
|
||||
}
|
||||
if result.Channel.Title != nil {
|
||||
ch.Title = *result.Channel.Title
|
||||
}
|
||||
}
|
||||
|
||||
return failed
|
||||
}
|
||||
|
||||
func (s *SelectChannelsForPurchase) Handle(b *bot.Bot, u *echotron.Update) {
|
||||
// Обрабатываем callback после успешного создания
|
||||
if u.CallbackQuery != nil && u.CallbackQuery.Data == "done" {
|
||||
// Возвращаемся к списку закупов
|
||||
if s.BackState != nil {
|
||||
b.SetState(s.BackState, bot.EditMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SelectChannelsForPurchase) Exit() {}
|
||||
|
||||
func channelLabel(ch PurchaseChannelInput) string {
|
||||
if ch.Username != "" {
|
||||
return "@" + ch.Username
|
||||
}
|
||||
if ch.Title != "" {
|
||||
return ch.Title
|
||||
}
|
||||
if ch.InviteLink != "" {
|
||||
return formatInviteLabel(ch.InviteLink)
|
||||
}
|
||||
if ch.ChannelID != "" {
|
||||
return ch.ChannelID
|
||||
}
|
||||
return "канал"
|
||||
}
|
||||
|
||||
func channelKey(ch PurchaseChannelInput) string {
|
||||
if ch.ChannelID != "" {
|
||||
return ch.ChannelID
|
||||
}
|
||||
if ch.Username != "" {
|
||||
return ch.Username
|
||||
}
|
||||
if ch.InviteLink != "" {
|
||||
return ch.InviteLink
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func channelLabelByKey(channels []PurchaseChannelInput, key string) string {
|
||||
for _, ch := range channels {
|
||||
if channelKey(ch) == key {
|
||||
return channelLabel(ch)
|
||||
}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func isInviteLink(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
if strings.Contains(value, "t.me/") || strings.Contains(value, "telegram.me/") {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(value, "tg://") || strings.HasPrefix(value, "tg:") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func formatInviteLabel(inviteLink string) string {
|
||||
link := strings.TrimSpace(inviteLink)
|
||||
if link == "" {
|
||||
return "приватный канал"
|
||||
}
|
||||
|
||||
code := link
|
||||
if strings.HasPrefix(code, "tg://") || strings.HasPrefix(code, "tg:") {
|
||||
if idx := strings.Index(code, "invite="); idx != -1 {
|
||||
code = code[idx+len("invite="):]
|
||||
if end := strings.IndexAny(code, "&?#"); end != -1 {
|
||||
code = code[:end]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
code = strings.TrimPrefix(code, "https://")
|
||||
code = strings.TrimPrefix(code, "http://")
|
||||
code = strings.TrimPrefix(code, "t.me/")
|
||||
code = strings.TrimPrefix(code, "telegram.me/")
|
||||
code = strings.TrimPrefix(code, "joinchat/")
|
||||
code = strings.TrimPrefix(code, "+")
|
||||
if idx := strings.LastIndex(code, "/"); idx != -1 {
|
||||
code = code[idx+1:]
|
||||
}
|
||||
if end := strings.IndexAny(code, "?#"); end != -1 {
|
||||
code = code[:end]
|
||||
}
|
||||
}
|
||||
|
||||
code = strings.Trim(code, "/+ ")
|
||||
if code == "" {
|
||||
return "приватный канал"
|
||||
}
|
||||
if len(code) > 10 {
|
||||
code = code[:6] + "..." + code[len(code)-2:]
|
||||
}
|
||||
return "приватный: " + code
|
||||
}
|
||||
Reference in New Issue
Block a user