приватные каналы

This commit is contained in:
Artem Tsyrulnikov
2026-01-21 02:41:20 +03:00
parent 233afb5451
commit b5695360c2
27 changed files with 1026 additions and 217 deletions

View File

@@ -521,7 +521,7 @@ func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderM
case "cost_value":
text += "<b>Ввод стоимости</b>\n\nНапример: <code>15000</code>"
if s.CurrentChannel != "" {
text += fmt.Sprintf("\n\nКанал: <b>@%s</b>", s.CurrentChannel)
text += fmt.Sprintf("\n\nКанал: <b>%s</b>", channelLabelByKey(s.Channels, s.CurrentChannel))
}
typeLabel := s.costTypeLabelForCurrent()
@@ -539,12 +539,12 @@ func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderM
case "cost_before_value":
text += "<b>Ввод стоимости до торга</b>\n\nНапример: <code>20000</code>"
if s.CurrentChannel != "" {
text += fmt.Sprintf("\n\nКанал: <b>@%s</b>", s.CurrentChannel)
text += fmt.Sprintf("\n\nКанал: <b>%s</b>", channelLabelByKey(s.Channels, s.CurrentChannel))
}
case "format_select":
text = "<i>Страница ввода формата размещения</i>\n\n"
if s.CurrentChannel != "" {
text += fmt.Sprintf("Канал: <b>@%s</b>\n\n", s.CurrentChannel)
text += fmt.Sprintf("Канал: <b>%s</b>\n\n", channelLabelByKey(s.Channels, s.CurrentChannel))
}
text += "Выберите формат\n\n"
keyboard := Keyboard(
@@ -707,7 +707,7 @@ func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.Rende
// Список каналов с кнопками в зависимости от режима
for i := start; i < end; i++ {
label := fmt.Sprintf("%d", i+1)
channelUsername := s.Channels[i].Username
channelKey := channelKey(s.Channels[i])
if s.ChannelEditMode == "copy" {
// Режим копирования
@@ -723,7 +723,7 @@ func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.Rende
}
// Кнопка удаления - только если есть данные у канала
if s.hasChannelValue(param, channelUsername) {
if s.hasChannelValue(param, channelKey) {
channelRow = append(channelRow, Button("⌫", fmt.Sprintf("param_clear:%s:%d", param, i)))
} else {
channelRow = append(channelRow, Button(" ", "noop"))
@@ -736,7 +736,7 @@ func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.Rende
channelRow = append(channelRow, Button(label, fmt.Sprintf("param_edit:%s:%d", param, i)))
// Кнопка "Добавить" или "Изменить" в зависимости от наличия данных
hasValue := s.hasChannelValue(param, channelUsername)
hasValue := s.hasChannelValue(param, channelKey)
if hasValue {
channelRow = append(channelRow, Button("Изменить", fmt.Sprintf("param_edit:%s:%d", param, i)))
} else {
@@ -772,8 +772,9 @@ func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.Rende
func (s *PurchaseOptionalDetails) renderParamChannelSummary(param string) string {
var lines []string
for i, ch := range s.Channels {
value := s.formatParamValue(param, ch.Username)
lines = append(lines, fmt.Sprintf("%d. @%s — %s", i+1, ch.Username, value))
channelKey := channelKey(ch)
value := s.formatParamValue(param, channelKey)
lines = append(lines, fmt.Sprintf("%d. %s — %s", i+1, channelLabel(ch), value))
}
return strings.Join(lines, "\n")
}
@@ -788,7 +789,7 @@ func (s *PurchaseOptionalDetails) handleParamCallback(b *bot.Bot, data string) b
}
if index >= 0 && index < len(s.Channels) {
s.CurrentParam = param
s.CurrentChannel = s.Channels[index].Username
s.CurrentChannel = channelKey(s.Channels[index])
s.openChannelEditor(b, param, s.CurrentChannel)
return true
}
@@ -801,8 +802,8 @@ func (s *PurchaseOptionalDetails) handleParamCallback(b *bot.Bot, data string) b
return true
}
if index >= 0 && index < len(s.Channels) {
username := s.Channels[index].Username
s.copyParamValue(param, username)
channelKey := channelKey(s.Channels[index])
s.copyParamValue(param, channelKey)
s.Enter(b, bot.EditMessage)
return true
}
@@ -815,8 +816,8 @@ func (s *PurchaseOptionalDetails) handleParamCallback(b *bot.Bot, data string) b
return true
}
if index >= 0 && index < len(s.Channels) {
username := s.Channels[index].Username
s.pasteParamValue(param, username)
channelKey := channelKey(s.Channels[index])
s.pasteParamValue(param, channelKey)
s.Enter(b, bot.EditMessage)
return true
}
@@ -829,8 +830,8 @@ func (s *PurchaseOptionalDetails) handleParamCallback(b *bot.Bot, data string) b
return true
}
if index >= 0 && index < len(s.Channels) {
username := s.Channels[index].Username
s.clearParamValue(param, username)
channelKey := channelKey(s.Channels[index])
s.clearParamValue(param, channelKey)
s.Enter(b, bot.EditMessage)
return true
}
@@ -902,31 +903,31 @@ func (s *PurchaseOptionalDetails) openCommonEditor(b *bot.Bot, param string) {
}
}
func (s *PurchaseOptionalDetails) openChannelEditor(b *bot.Bot, param, username string) {
func (s *PurchaseOptionalDetails) openChannelEditor(b *bot.Bot, param, channelKey string) {
switch param {
case "placement":
s.InputMode = "placement_channels"
b.SetState(ui2.NewDateTimePicker(ui2.DateTimePickerConfig{
Title: "Дата и время размещения",
Key: "placement_datetime:" + username,
Key: "placement_datetime:" + channelKey,
IncludeTime: true,
Selected: s.PlacementByChannel[username],
Selected: s.PlacementByChannel[channelKey],
BackState: s,
}), bot.EditMessage)
case "cost":
s.InputMode = "cost_value"
s.ReturnMode = "cost_channels"
s.CurrentChannel = username
s.CurrentChannel = channelKey
s.Enter(b, bot.EditMessage)
case "cost_before":
s.InputMode = "cost_before_value"
s.ReturnMode = "cost_before_channels"
s.CurrentChannel = username
s.CurrentChannel = channelKey
s.Enter(b, bot.EditMessage)
case "format":
s.InputMode = "format_select"
s.ReturnMode = "format_channels"
s.CurrentChannel = username
s.CurrentChannel = channelKey
s.Enter(b, bot.EditMessage)
default:
s.InputMode = ""
@@ -934,12 +935,12 @@ func (s *PurchaseOptionalDetails) openChannelEditor(b *bot.Bot, param, username
}
}
func (s *PurchaseOptionalDetails) copyParamValue(param, username string) {
func (s *PurchaseOptionalDetails) copyParamValue(param, channelKey string) {
switch param {
case "placement":
s.PlacementCopy = s.PlacementByChannel[username]
s.PlacementCopy = s.PlacementByChannel[channelKey]
case "cost":
entry := s.CostByChannel[username]
entry := s.CostByChannel[channelKey]
copied := entry
if entry.Value == nil {
copied.Value = nil
@@ -949,9 +950,9 @@ func (s *PurchaseOptionalDetails) copyParamValue(param, username string) {
}
s.CostCopy = &copied
case "cost_before":
s.CostBeforeCopy = s.CostBeforeByChannel[username]
s.CostBeforeCopy = s.CostBeforeByChannel[channelKey]
case "format":
if value, ok := s.FormatByChannel[username]; ok {
if value, ok := s.FormatByChannel[channelKey]; ok {
copied := value
s.FormatCopy = &copied
} else {
@@ -960,18 +961,18 @@ func (s *PurchaseOptionalDetails) copyParamValue(param, username string) {
}
}
func (s *PurchaseOptionalDetails) pasteParamValue(param, username string) {
func (s *PurchaseOptionalDetails) pasteParamValue(param, channelKey string) {
switch param {
case "placement":
if s.PlacementCopy == nil {
s.PlacementByChannel[username] = nil
s.PlacementByChannel[channelKey] = nil
return
}
value := s.PlacementCopy.In(ui2.MskLocation)
s.PlacementByChannel[username] = &value
s.PlacementByChannel[channelKey] = &value
case "cost":
if s.CostCopy == nil {
delete(s.CostByChannel, username)
delete(s.CostByChannel, channelKey)
return
}
copied := *s.CostCopy
@@ -979,20 +980,20 @@ func (s *PurchaseOptionalDetails) pasteParamValue(param, username string) {
value := *copied.Value
copied.Value = &value
}
s.CostByChannel[username] = copied
s.CostByChannel[channelKey] = copied
case "cost_before":
if s.CostBeforeCopy == nil {
s.CostBeforeByChannel[username] = nil
s.CostBeforeByChannel[channelKey] = nil
return
}
value := *s.CostBeforeCopy
s.CostBeforeByChannel[username] = &value
s.CostBeforeByChannel[channelKey] = &value
case "format":
if s.FormatCopy == nil {
delete(s.FormatByChannel, username)
delete(s.FormatByChannel, channelKey)
return
}
s.FormatByChannel[username] = *s.FormatCopy
s.FormatByChannel[channelKey] = *s.FormatCopy
}
}
@@ -1000,33 +1001,33 @@ func (s *PurchaseOptionalDetails) applyParamToAll(param string) {
switch param {
case "placement":
for _, ch := range s.Channels {
s.pasteParamValue(param, ch.Username)
s.pasteParamValue(param, channelKey(ch))
}
case "cost":
for _, ch := range s.Channels {
s.pasteParamValue(param, ch.Username)
s.pasteParamValue(param, channelKey(ch))
}
case "cost_before":
for _, ch := range s.Channels {
s.pasteParamValue(param, ch.Username)
s.pasteParamValue(param, channelKey(ch))
}
case "format":
for _, ch := range s.Channels {
s.pasteParamValue(param, ch.Username)
s.pasteParamValue(param, channelKey(ch))
}
}
}
func (s *PurchaseOptionalDetails) clearParamValue(param, username string) {
func (s *PurchaseOptionalDetails) clearParamValue(param, channelKey string) {
switch param {
case "placement":
s.PlacementByChannel[username] = nil
s.PlacementByChannel[channelKey] = nil
case "cost":
delete(s.CostByChannel, username)
delete(s.CostByChannel, channelKey)
case "cost_before":
s.CostBeforeByChannel[username] = nil
s.CostBeforeByChannel[channelKey] = nil
case "format":
delete(s.FormatByChannel, username)
delete(s.FormatByChannel, channelKey)
}
}
@@ -1116,7 +1117,10 @@ func (s *PurchaseOptionalDetails) ensureDefaults() {
func (s *PurchaseOptionalDetails) syncChannelMaps() {
valid := make(map[string]struct{}, len(s.Channels))
for _, ch := range s.Channels {
valid[ch.Username] = struct{}{}
key := channelKey(ch)
if key != "" {
valid[key] = struct{}{}
}
}
for key := range s.PlacementByChannel {
if _, ok := valid[key]; !ok {
@@ -1196,17 +1200,17 @@ func (s *PurchaseOptionalDetails) formatFormatDetails() string {
return "\n" + s.renderParamChannelSummary("format")
}
func (s *PurchaseOptionalDetails) formatParamValue(param, username string) string {
func (s *PurchaseOptionalDetails) formatParamValue(param, channelKey string) string {
switch param {
case "placement":
value := s.PlacementByChannel[username]
value := s.PlacementByChannel[channelKey]
return s.formatDateTime(value)
case "cost":
return s.formatCostForChannel(username)
return s.formatCostForChannel(channelKey)
case "cost_before":
return s.formatCostBeforeForChannel(username)
return s.formatCostBeforeForChannel(channelKey)
case "format":
if value, ok := s.FormatByChannel[username]; ok && value != "" {
if value, ok := s.FormatByChannel[channelKey]; ok && value != "" {
return value
}
return "—"
@@ -1215,8 +1219,8 @@ func (s *PurchaseOptionalDetails) formatParamValue(param, username string) strin
}
}
func (s *PurchaseOptionalDetails) formatCostForChannel(username string) string {
entry, ok := s.CostByChannel[username]
func (s *PurchaseOptionalDetails) formatCostForChannel(channelKey string) string {
entry, ok := s.CostByChannel[channelKey]
if !ok || entry.Value == nil {
return "—"
}
@@ -1224,8 +1228,8 @@ func (s *PurchaseOptionalDetails) formatCostForChannel(username string) string {
return fmt.Sprintf("%s %.0f₽", label, *entry.Value)
}
func (s *PurchaseOptionalDetails) formatCostBeforeForChannel(username string) string {
value, ok := s.CostBeforeByChannel[username]
func (s *PurchaseOptionalDetails) formatCostBeforeForChannel(channelKey string) string {
value, ok := s.CostBeforeByChannel[channelKey]
if !ok || value == nil {
return "—"
}
@@ -1247,19 +1251,19 @@ func (s *PurchaseOptionalDetails) hasCopyBuffer(param string) bool {
}
}
func (s *PurchaseOptionalDetails) hasChannelValue(param, username string) bool {
func (s *PurchaseOptionalDetails) hasChannelValue(param, channelKey string) bool {
switch param {
case "placement":
value, ok := s.PlacementByChannel[username]
value, ok := s.PlacementByChannel[channelKey]
return ok && value != nil
case "cost":
entry, ok := s.CostByChannel[username]
entry, ok := s.CostByChannel[channelKey]
return ok && entry.Value != nil
case "cost_before":
value, ok := s.CostBeforeByChannel[username]
value, ok := s.CostBeforeByChannel[channelKey]
return ok && value != nil
case "format":
value, ok := s.FormatByChannel[username]
value, ok := s.FormatByChannel[channelKey]
return ok && value != ""
default:
return false
@@ -1412,14 +1416,14 @@ func (s *PurchaseOptionalDetails) createPurchase(b *bot.Bot, jwt string) {
apiChannels := make([]backend.CreatePlacementChannelInput, 0, len(s.Channels))
for _, ch := range s.Channels {
channelDetails := s.buildChannelDetails(ch.Username)
channelDetails := s.buildChannelDetails(channelKey(ch))
if channelDetails != nil && s.isChannelDetailsEmpty(channelDetails) {
channelDetails = nil
}
apiChannels = append(apiChannels, backend.CreatePlacementChannelInput{
Username: ch.Username,
Comment: ch.Comment,
Details: channelDetails,
ChannelID: ch.ChannelID,
Comment: ch.Comment,
Details: channelDetails,
})
}
@@ -1495,29 +1499,29 @@ func (s *PurchaseOptionalDetails) buildPlacementDetails(placementType *string) *
return details
}
func (s *PurchaseOptionalDetails) buildChannelDetails(username string) *backend.PlacementDetails {
func (s *PurchaseOptionalDetails) buildChannelDetails(channelKey string) *backend.PlacementDetails {
if len(s.Channels) <= 1 {
return nil
}
details := &backend.PlacementDetails{}
if s.PlacementMode == "per_channel" {
if value, ok := s.PlacementByChannel[username]; ok && value != nil {
if value, ok := s.PlacementByChannel[channelKey]; ok && value != nil {
formatted := value.Format(time.RFC3339)
details.PlacementAt = &formatted
}
}
if s.CostMode == "per_channel" {
entry := s.CostByChannel[username]
entry := s.CostByChannel[channelKey]
details.Cost = s.buildCostInfo(entry.Type, entry.Value)
}
if s.CostBeforeMode == "per_channel" {
if value, ok := s.CostBeforeByChannel[username]; ok && value != nil {
if value, ok := s.CostBeforeByChannel[channelKey]; ok && value != nil {
details.CostBeforeBargain = value
}
}
if s.FormatMode == "per_channel" {
if value, ok := s.FormatByChannel[username]; ok && value != "" {
if value, ok := s.FormatByChannel[channelKey]; ok && value != "" {
details.Format = &value
}
}