размещения ручки

This commit is contained in:
Artem Tsyrulnikov
2026-01-20 20:15:18 +03:00
parent 33c9121571
commit e8ff317566
35 changed files with 949 additions and 290 deletions

View File

@@ -21,7 +21,7 @@ type AddCreativeCtx struct {
BackState bot.State
}
type AddCreativeStart struct{ ctx *AddCreativeCtx }
type AddCreativeStart struct{ Ctx *AddCreativeCtx }
type AddCreativeEdit struct{ ctx *AddCreativeCtx }
@@ -102,8 +102,8 @@ func (s *AddCreativeStart) HandleCallback(b *bot.Bot, u *echotron.Update) {
if u.CallbackQuery == nil || u.CallbackQuery.Data == "" {
return
}
if u.CallbackQuery.Data == "cancel" && s.ctx.BackState != nil {
b.SetState(s.ctx.BackState, bot.EditMessage)
if u.CallbackQuery.Data == "cancel" && s.Ctx.BackState != nil {
b.SetState(s.Ctx.BackState, bot.EditMessage)
}
}
@@ -112,14 +112,14 @@ func (s *AddCreativeStart) HandleMessage(b *bot.Bot, u *echotron.Update) {
return
}
s.ctx.extractCreativeFromMessage(u.Message)
if s.ctx.Name == nil {
name := s.ctx.generateCreativeName()
s.ctx.Name = &name
s.Ctx.extractCreativeFromMessage(u.Message)
if s.Ctx.Name == nil {
name := s.Ctx.generateCreativeName()
s.Ctx.Name = &name
}
s.ctx.DeleteUserMessage(b, u.Message.ID)
b.SetState(&AddCreativeEdit{ctx: s.ctx}, bot.NewMessage)
s.Ctx.DeleteUserMessage(b, u.Message.ID)
b.SetState(&AddCreativeEdit{ctx: s.Ctx}, bot.NewMessage)
}
func (s *AddCreativeStart) Handle(_ *bot.Bot, _ *echotron.Update) {}

View File

@@ -109,7 +109,7 @@ func (s *Creatives) HandleCallback(b *bot.Bot, u *echotron.Update) {
b.Edit("📦 Архив креативов\n\nЭта функция в разработке", Keyboard(Row(Button("← Назад", "back"))))
case data == "add_creative":
b.SetState(&AddCreativeStart{ctx: &AddCreativeCtx{
b.SetState(&AddCreativeStart{Ctx: &AddCreativeCtx{
ProjectID: s.ProjectID,
BackState: s,
}}, bot.EditMessage)

View File

@@ -28,10 +28,7 @@ func (s *PlacementDetails) renderDetails(b *bot.Bot, mode bot.RenderMode) {
return
}
var placementPost *backend.PlacementPostOutput
if len(placement.PlacementPosts) > 0 {
placementPost = &placement.PlacementPosts[0]
}
placementPost := placement.PlacementPost
text := "<b>Детали размещения</b>\n\n"
@@ -69,12 +66,11 @@ func (s *PlacementDetails) renderDetails(b *bot.Bot, mode bot.RenderMode) {
if placementPost != nil {
text += "\n<b>Пост</b>\n"
if placementPost.AdPostURL != nil && *placementPost.AdPostURL != "" {
text += fmt.Sprintf("• <b>Ссылка на пост:</b> %s\n", *placementPost.AdPostURL)
if placementPost.Post.URL != nil && *placementPost.Post.URL != "" {
text += fmt.Sprintf("• <b>Ссылка на пост:</b> %s\n", *placementPost.Post.URL)
} else {
text += "• <b>Ссылка на пост:</b> не найдена\n"
}
text += fmt.Sprintf("• <b>Статус поста:</b> %s\n", formatPlacementPostStatus(placementPost.Status))
}
keyboard := Keyboard(Row(Button("← Назад", "back")))
@@ -128,18 +124,6 @@ func formatPlacementType(value string) string {
}
}
func formatPlacementPostStatus(status string) string {
normalized := strings.TrimSpace(strings.ToLower(status))
switch normalized {
case "active":
return "Активен"
case "archived":
return "Архивирован"
default:
return status
}
}
func formatCostType(value string) string {
switch strings.TrimSpace(strings.ToLower(value)) {
case "cpm":

View File

@@ -3,7 +3,6 @@ package screens
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"time"
@@ -1425,7 +1424,7 @@ func (s *PurchaseOptionalDetails) createPurchase(b *bot.Bot, jwt string) {
}
input := backend.CreatePlacementsInput{
CreativeID: s.CreativeID,
CreativeID: &s.CreativeID,
Channels: apiChannels,
Details: placementDetails,
}
@@ -1439,20 +1438,24 @@ func (s *PurchaseOptionalDetails) createPurchase(b *bot.Bot, jwt string) {
return
}
creative, err := b.Backend.GetCreative(context.Background(), jwt, b.Session.WorkspaceID, s.CreativeID)
if err != nil {
log.Error().Err(err).Msg("Failed to get creative")
creative = nil
}
if creative != nil {
for _, placement := range placements.Placements {
channelName := s.formatChannelName(placement)
header := fmt.Sprintf("<b>Канал:</b> %s", channelName)
b.SendMessage(header, b.ChatID, &echotron.MessageOptions{ParseMode: echotron.HTML})
s.sendCreativeWithInviteLink(b, creative, placement.InviteLink)
for _, placement := range placements.Placements {
creativePreview, err := b.Backend.BuildPlacementCreative(
context.Background(),
jwt,
b.Session.WorkspaceID,
s.ProjectID,
placement.ID,
)
if err != nil {
log.Error().Err(err).Msg("Failed to build placement creative")
continue
}
channelName := s.formatChannelName(placement)
header := fmt.Sprintf("<b>Канал:</b> %s", channelName)
b.SendMessage(header, b.ChatID, &echotron.MessageOptions{ParseMode: echotron.HTML})
s.sendCreativePreview(b, creativePreview)
}
b.SetState(&Placements{
@@ -1588,15 +1591,12 @@ func (s *PurchaseOptionalDetails) formatChannelName(placement backend.PlacementO
return "Без названия"
}
func (s *PurchaseOptionalDetails) sendCreativeWithInviteLink(
func (s *PurchaseOptionalDetails) sendCreativePreview(
b *bot.Bot,
creative *backend.Creative,
inviteLink string,
creative *backend.CreativePreviewOutput,
) {
log.Info().Str("invite_link", inviteLink).Msg("Sending creative with invite link")
linkedText := s.injectInviteLink(creative.Text, inviteLink)
editor := CreativeEditorFields{
Text: &linkedText,
Text: &creative.Text,
MediaType: creative.MediaType,
MediaFileID: creative.MediaFileID,
}
@@ -1604,34 +1604,12 @@ func (s *PurchaseOptionalDetails) sendCreativeWithInviteLink(
if len(creative.Buttons) > 0 {
editor.Buttons = make([]InlineButton, 0, len(creative.Buttons))
for _, btn := range creative.Buttons {
url := btn.URL
if url == inviteLinkPlaceholder {
url = inviteLink
}
editor.Buttons = append(editor.Buttons, InlineButton{
Text: btn.Text,
URL: url,
URL: btn.URL,
})
}
}
editor.SendCreativePreview(b)
}
func (s *PurchaseOptionalDetails) injectInviteLink(text, inviteLink string) string {
if text == "" {
return text
}
re := regexp.MustCompile(`<a\s+class="tg-link">([^<]*)</a>`)
return re.ReplaceAllStringFunc(text, func(match string) string {
sub := re.FindStringSubmatch(match)
if len(sub) < 2 {
return match
}
inner := strings.TrimSpace(sub[1])
if inner == "" {
return fmt.Sprintf(`<a href="%s">%s</a>`, inviteLink, inviteLink)
}
return fmt.Sprintf(`<a href="%s">%s</a>`, inviteLink, sub[1])
})
}

View File

@@ -249,7 +249,7 @@ func (s *SelectChannelsForPurchase) createPurchase(b *bot.Bot, jwt string) {
}
input := backend.CreatePlacementsInput{
CreativeID: s.CreativeID,
CreativeID: &s.CreativeID,
Channels: apiChannels,
}