refactor
This commit is contained in:
@@ -18,16 +18,16 @@ var emptyKeyboard = echotron.InlineKeyboardMarkup{
|
|||||||
InlineKeyboard: [][]echotron.InlineKeyboardButton{},
|
InlineKeyboard: [][]echotron.InlineKeyboardButton{},
|
||||||
}
|
}
|
||||||
|
|
||||||
type Exec struct {
|
type exec struct {
|
||||||
handled bool
|
handled bool // Событие обработано, дальше не идём
|
||||||
transitioned bool
|
transitioned bool // Был SetState
|
||||||
}
|
}
|
||||||
|
|
||||||
type Bot struct {
|
type Bot struct {
|
||||||
echotron.API
|
echotron.API
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
ChatID int64
|
ChatID int64
|
||||||
exec *Exec
|
exec *exec
|
||||||
CurrentState State
|
CurrentState State
|
||||||
Session *Session
|
Session *Session
|
||||||
LastMessageID int // ID последнего сообщения с inline кнопками
|
LastMessageID int // ID последнего сообщения с inline кнопками
|
||||||
@@ -101,14 +101,8 @@ func (b *Bot) GetOrCreateJWT(u *echotron.Update) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) SetState(s State, mode RenderMode) {
|
func (b *Bot) SetState(s State, mode RenderMode) {
|
||||||
log.Info().
|
log.Info().Msg(fmt.Sprintf("State transition: %T -> %T", b.CurrentState, s))
|
||||||
Int64("chat_id", b.ChatID).
|
|
||||||
Str("from_state", fmt.Sprintf("%T", b.CurrentState)).
|
|
||||||
Str("to_state", fmt.Sprintf("%T", s)).
|
|
||||||
Str("mode", fmt.Sprintf("%v", mode)).
|
|
||||||
Msg("State transition")
|
|
||||||
|
|
||||||
// Вызываем Exit() у старого состояния для cleanup (например, отмены горутин)
|
|
||||||
if b.CurrentState != nil {
|
if b.CurrentState != nil {
|
||||||
b.CurrentState.Exit()
|
b.CurrentState.Exit()
|
||||||
}
|
}
|
||||||
@@ -123,193 +117,6 @@ func (b *Bot) SetState(s State, mode RenderMode) {
|
|||||||
s.Enter(b, mode)
|
s.Enter(b, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) forwardSystemEvent(u *echotron.Update) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case u.ChatJoinRequest != nil:
|
|
||||||
req := u.ChatJoinRequest
|
|
||||||
if !isChannelChat(req.Chat.Type) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
inviteLink := ""
|
|
||||||
if req.InviteLink != nil {
|
|
||||||
inviteLink = req.InviteLink.InviteLink
|
|
||||||
}
|
|
||||||
if inviteLink == "" {
|
|
||||||
log.Debug().Msg("No invite link in chat join request")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userID := req.From.ID
|
|
||||||
username := stringPtr(req.From.Username)
|
|
||||||
firstName := stringPtr(req.From.FirstName)
|
|
||||||
lastName := stringPtr(req.From.LastName)
|
|
||||||
if userID == 0 && req.UserChatID != 0 {
|
|
||||||
userID = req.UserChatID
|
|
||||||
}
|
|
||||||
if userID == 0 {
|
|
||||||
log.Error().Msg("No user id in chat join request")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := map[string]any{
|
|
||||||
"type": "subscription",
|
|
||||||
"user_telegram_id": userID,
|
|
||||||
"invite_link": inviteLink,
|
|
||||||
"username": username,
|
|
||||||
"first_name": firstName,
|
|
||||||
"last_name": lastName,
|
|
||||||
}
|
|
||||||
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to send subscription event")
|
|
||||||
}
|
|
||||||
|
|
||||||
case u.ChatMember != nil:
|
|
||||||
event := u.ChatMember
|
|
||||||
if !isChannelChat(event.Chat.Type) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
memberUser := event.NewChatMember.User
|
|
||||||
if memberUser == nil {
|
|
||||||
log.Error().Msg("No user in chat member update")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
oldStatus := event.OldChatMember.Status
|
|
||||||
newStatus := event.NewChatMember.Status
|
|
||||||
|
|
||||||
wasNotMember := oldStatus == "left" || oldStatus == "kicked"
|
|
||||||
isNowMember := newStatus == "member" || newStatus == "administrator" || newStatus == "creator"
|
|
||||||
userJoined := wasNotMember && isNowMember
|
|
||||||
|
|
||||||
wasMember := oldStatus == "member" || oldStatus == "administrator" || oldStatus == "creator" || oldStatus == "restricted"
|
|
||||||
isNowNotMember := newStatus == "left" || newStatus == "kicked"
|
|
||||||
userLeft := wasMember && isNowNotMember
|
|
||||||
|
|
||||||
if userJoined {
|
|
||||||
inviteLink := ""
|
|
||||||
if event.InviteLink != nil {
|
|
||||||
inviteLink = event.InviteLink.InviteLink
|
|
||||||
}
|
|
||||||
if inviteLink == "" {
|
|
||||||
log.Debug().Msg("No invite link in chat member update")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
username := stringPtr(memberUser.Username)
|
|
||||||
firstName := stringPtr(memberUser.FirstName)
|
|
||||||
lastName := stringPtr(memberUser.LastName)
|
|
||||||
payload := map[string]any{
|
|
||||||
"type": "subscription",
|
|
||||||
"user_telegram_id": memberUser.ID,
|
|
||||||
"invite_link": inviteLink,
|
|
||||||
"username": username,
|
|
||||||
"first_name": firstName,
|
|
||||||
"last_name": lastName,
|
|
||||||
}
|
|
||||||
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to send subscription event")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if userLeft {
|
|
||||||
payload := map[string]any{
|
|
||||||
"type": "unsubscription",
|
|
||||||
"user_telegram_id": memberUser.ID,
|
|
||||||
"channel_telegram_id": event.Chat.ID,
|
|
||||||
}
|
|
||||||
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to send unsubscription event")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case u.MyChatMember != nil:
|
|
||||||
event := u.MyChatMember
|
|
||||||
if !isChannelChat(event.Chat.Type) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
actorID := event.From.ID
|
|
||||||
if actorID == 0 {
|
|
||||||
log.Error().Msg("No user in my chat member update")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
oldStatus := event.OldChatMember.Status
|
|
||||||
newStatus := event.NewChatMember.Status
|
|
||||||
|
|
||||||
wasMember := oldStatus == "administrator" || oldStatus == "member"
|
|
||||||
isNowNotMember := newStatus == "left" || newStatus == "kicked"
|
|
||||||
botRemoved := wasMember && isNowNotMember
|
|
||||||
|
|
||||||
permissionsChanged := oldStatus == "administrator" && newStatus == "administrator"
|
|
||||||
|
|
||||||
if botRemoved {
|
|
||||||
payload := map[string]any{
|
|
||||||
"type": "bot_removed",
|
|
||||||
"telegram_id": event.Chat.ID,
|
|
||||||
"user_telegram_id": actorID,
|
|
||||||
}
|
|
||||||
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to send bot removed event")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if permissionsChanged {
|
|
||||||
title := event.Chat.Title
|
|
||||||
if title == "" {
|
|
||||||
title = fmt.Sprintf("Channel %d", event.Chat.ID)
|
|
||||||
}
|
|
||||||
payload := map[string]any{
|
|
||||||
"type": "bot_permissions",
|
|
||||||
"telegram_id": event.Chat.ID,
|
|
||||||
"chat_title": title,
|
|
||||||
"user_telegram_id": actorID,
|
|
||||||
"permissions": map[string]any{
|
|
||||||
"is_admin": newStatus == "administrator",
|
|
||||||
"can_invite_users": event.NewChatMember.CanInviteUsers,
|
|
||||||
"can_restrict_members": event.NewChatMember.CanRestrictMembers,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to send bot permissions event")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wasNotMember := oldStatus == "left" || oldStatus == "kicked"
|
|
||||||
isNowMember := newStatus == "administrator" || newStatus == "member"
|
|
||||||
botAdded := wasNotMember && isNowMember
|
|
||||||
|
|
||||||
if botAdded {
|
|
||||||
title := event.Chat.Title
|
|
||||||
if title == "" {
|
|
||||||
title = fmt.Sprintf("Channel %d", event.Chat.ID)
|
|
||||||
}
|
|
||||||
payload := map[string]any{
|
|
||||||
"type": "bot_added",
|
|
||||||
"telegram_id": event.Chat.ID,
|
|
||||||
"title": title,
|
|
||||||
"username": stringPtr(event.Chat.Username),
|
|
||||||
"user_telegram_id": actorID,
|
|
||||||
"bot_permissions": map[string]any{
|
|
||||||
"is_admin": newStatus == "administrator",
|
|
||||||
"can_invite_users": event.NewChatMember.CanInviteUsers,
|
|
||||||
"can_restrict_members": event.NewChatMember.CanRestrictMembers,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to send bot added event")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isChannelChat(chatType string) bool {
|
func isChannelChat(chatType string) bool {
|
||||||
return chatType == "channel" || chatType == "supergroup"
|
return chatType == "channel" || chatType == "supergroup"
|
||||||
}
|
}
|
||||||
@@ -322,10 +129,13 @@ func stringPtr(value string) *string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) Render(text string, keyboard echotron.InlineKeyboardMarkup, mode RenderMode) {
|
func (b *Bot) Render(text string, keyboard echotron.InlineKeyboardMarkup, mode RenderMode) {
|
||||||
if mode == EditMessage {
|
switch mode {
|
||||||
|
case EditMessage:
|
||||||
b.Edit(text, keyboard)
|
b.Edit(text, keyboard)
|
||||||
} else {
|
case NewMessage:
|
||||||
b.SendNew(text, keyboard)
|
b.SendNew(text, keyboard)
|
||||||
|
default:
|
||||||
|
panic("unknown RenderMode")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,131 +155,82 @@ func (b *Bot) DownloadFileBytes(fileID string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) SendNew(text string, keyboard echotron.InlineKeyboardMarkup) {
|
func (b *Bot) SendNew(text string, keyboard echotron.InlineKeyboardMarkup) {
|
||||||
|
if b.exec != nil {
|
||||||
|
defer func() { b.exec.handled = true }()
|
||||||
|
}
|
||||||
|
|
||||||
if b.LastMessageID != 0 {
|
if b.LastMessageID != 0 {
|
||||||
log.Info().Int("cleanup_msg_id", b.LastMessageID).Msg("Cleaning up keyboard before SendNew")
|
log.Info().Int("cleanup_msg_id", b.LastMessageID).Msg("Cleaning up keyboard before SendNew")
|
||||||
b.cleanupKeyboard(b.LastMessageID)
|
b.cleanupKeyboard(b.LastMessageID)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := b.SendMessage(text, b.ChatID, &echotron.MessageOptions{
|
res, err := b.SendMessage(text, b.ChatID, &echotron.MessageOptions{ReplyMarkup: keyboard, ParseMode: echotron.HTML})
|
||||||
ReplyMarkup: keyboard,
|
|
||||||
ParseMode: echotron.HTML,
|
|
||||||
})
|
|
||||||
|
|
||||||
if b.exec != nil {
|
|
||||||
b.exec.handled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("SendMessage failed")
|
log.Error().Err(err).Msg("SendMessage failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.Result != nil {
|
if res.Result == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
b.LastMessageID = res.Result.ID
|
b.LastMessageID = res.Result.ID
|
||||||
b.LastMessageIsMedia = false
|
b.LastMessageIsMedia = false
|
||||||
b.LastMessageTextHash = hashString(text)
|
b.LastMessageTextHash = hashString(text)
|
||||||
b.LastMessageKBHash = keyboardHash(keyboard)
|
b.LastMessageKBHash = keyboardHash(keyboard)
|
||||||
log.Info().Int("new_last_msg_id", b.LastMessageID).Msg("Updated LastMessageID in SendNew")
|
log.Info().Int("new_last_msg_id", b.LastMessageID).Msg("Updated LastMessageID in SendNew")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) Edit(text string, keyboard echotron.InlineKeyboardMarkup) {
|
func (b *Bot) Edit(text string, keyboard echotron.InlineKeyboardMarkup) {
|
||||||
if b.LastMessageID != 0 {
|
|
||||||
log.Info().
|
|
||||||
Int("last_msg_id", b.LastMessageID).
|
|
||||||
Int("keyboard_rows", len(keyboard.InlineKeyboard)).
|
|
||||||
Int("text_len", len(text)).
|
|
||||||
Msg("EditMessageText request")
|
|
||||||
textHash := hashString(text)
|
|
||||||
kbHash := keyboardHash(keyboard)
|
|
||||||
if textHash == b.LastMessageTextHash && kbHash == b.LastMessageKBHash {
|
|
||||||
log.Info().Msg("EditMessageText skipped: message is not modified")
|
|
||||||
if b.exec != nil {
|
if b.exec != nil {
|
||||||
b.exec.handled = true
|
defer func() { b.exec.handled = true }()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.LastMessageID == 0 {
|
||||||
|
b.SendNew(text, keyboard)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newTextHash := hashString(text)
|
||||||
|
newKBHash := keyboardHash(keyboard)
|
||||||
|
|
||||||
|
textChanged := newTextHash != b.LastMessageTextHash
|
||||||
|
kbChanged := newKBHash != b.LastMessageKBHash
|
||||||
|
|
||||||
|
if !textChanged && !kbChanged {
|
||||||
|
log.Info().Msg("Edit skipped: message not modified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
msgID := echotron.NewMessageID(b.ChatID, b.LastMessageID)
|
||||||
|
|
||||||
if b.LastMessageIsMedia {
|
if b.LastMessageIsMedia {
|
||||||
_, err = b.EditMessageCaption(
|
_, err = b.EditMessageCaption(msgID, &echotron.MessageCaptionOptions{Caption: text, ParseMode: echotron.HTML, ReplyMarkup: keyboard})
|
||||||
echotron.NewMessageID(b.ChatID, b.LastMessageID),
|
|
||||||
&echotron.MessageCaptionOptions{
|
|
||||||
Caption: text,
|
|
||||||
ParseMode: echotron.HTML,
|
|
||||||
ReplyMarkup: keyboard,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err == nil {
|
|
||||||
b.LastMessageTextHash = textHash
|
|
||||||
b.LastMessageKBHash = kbHash
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
_, err = b.EditMessageText(
|
_, err = b.EditMessageText(text, msgID, &echotron.MessageTextOptions{ReplyMarkup: keyboard, ParseMode: echotron.HTML})
|
||||||
text,
|
}
|
||||||
echotron.NewMessageID(b.ChatID, b.LastMessageID),
|
|
||||||
&echotron.MessageTextOptions{
|
|
||||||
ReplyMarkup: keyboard,
|
|
||||||
ParseMode: echotron.HTML,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
b.LastMessageTextHash = textHash
|
if textChanged {
|
||||||
b.LastMessageKBHash = kbHash
|
b.LastMessageTextHash = newTextHash
|
||||||
}
|
}
|
||||||
}
|
if kbChanged {
|
||||||
if err != nil {
|
b.LastMessageKBHash = newKBHash
|
||||||
if strings.Contains(err.Error(), "message is not modified") {
|
|
||||||
log.Err(err).Msg("EditMessageText ignored: message is not modified")
|
|
||||||
if b.exec != nil {
|
|
||||||
b.exec.handled = true
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.Contains(err.Error(), "no text in the message to edit") {
|
|
||||||
|
switch {
|
||||||
|
case strings.Contains(err.Error(), "message is not modified"):
|
||||||
|
log.Err(err).Msg("EditMessageText ignored: message is not modified")
|
||||||
|
case strings.Contains(err.Error(), "no text in the message to edit"):
|
||||||
b.LastMessageIsMedia = true
|
b.LastMessageIsMedia = true
|
||||||
log.Err(err).Msg("EditMessageText ignored: message has no text")
|
log.Err(err).Msg("EditMessageText ignored: message has no text")
|
||||||
return
|
default:
|
||||||
}
|
|
||||||
log.Error().Err(err).Msg("EditMessageText")
|
log.Error().Err(err).Msg("EditMessageText")
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
b.SendNew(text, keyboard)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.exec != nil {
|
|
||||||
b.exec.handled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bot) SendMessageWithURLButton(text, buttonText, url string) {
|
|
||||||
keyboard := echotron.InlineKeyboardMarkup{
|
|
||||||
InlineKeyboard: [][]echotron.InlineKeyboardButton{
|
|
||||||
{
|
|
||||||
{
|
|
||||||
Text: buttonText,
|
|
||||||
URL: url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := b.SendMessage(text, b.ChatID, &echotron.MessageOptions{
|
|
||||||
ReplyMarkup: keyboard,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("SendMessageWithURLButton")
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.exec != nil {
|
|
||||||
b.exec.handled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil && res.Result != nil {
|
|
||||||
b.LastMessageID = res.Result.ID
|
|
||||||
b.LastMessageIsMedia = false
|
|
||||||
b.LastMessageTextHash = hashString(text)
|
|
||||||
b.LastMessageKBHash = keyboardHash(keyboard)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyboardHash(kb echotron.InlineKeyboardMarkup) string {
|
func keyboardHash(kb echotron.InlineKeyboardMarkup) string {
|
||||||
@@ -490,13 +251,7 @@ func (b *Bot) Update(u *echotron.Update) {
|
|||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
log.Info().
|
log.Info().Int64("chat_id", b.ChatID).Msg("Update received")
|
||||||
Int64("chat_id", b.ChatID).
|
|
||||||
Str("current_state", fmt.Sprintf("%T", b.CurrentState)).
|
|
||||||
Bool("has_callback", u.CallbackQuery != nil).
|
|
||||||
Bool("has_message", u.Message != nil).
|
|
||||||
Bool("has_edited_message", u.EditedMessage != nil).
|
|
||||||
Msg("Update received")
|
|
||||||
|
|
||||||
// 1. Системные события -> отправляем доменные события в backend
|
// 1. Системные события -> отправляем доменные события в backend
|
||||||
if u.ChatJoinRequest != nil || u.ChatMember != nil || u.MyChatMember != nil {
|
if u.ChatJoinRequest != nil || u.ChatMember != nil || u.MyChatMember != nil {
|
||||||
@@ -533,8 +288,8 @@ func (b *Bot) Update(u *echotron.Update) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
exec := &Exec{}
|
e := &exec{}
|
||||||
b.exec = exec
|
b.exec = e
|
||||||
defer func() { b.exec = nil }()
|
defer func() { b.exec = nil }()
|
||||||
|
|
||||||
if b.CurrentState == nil {
|
if b.CurrentState == nil {
|
||||||
@@ -554,7 +309,7 @@ func (b *Bot) Update(u *echotron.Update) {
|
|||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
b.CurrentState.HandleMessage(b, u)
|
b.CurrentState.HandleMessage(b, u)
|
||||||
if exec.handled || exec.transitioned {
|
if e.handled || e.transitioned {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -575,14 +330,14 @@ func (b *Bot) Update(u *echotron.Update) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.CurrentState.HandleCallback(b, u)
|
b.CurrentState.HandleCallback(b, u)
|
||||||
if exec.handled || exec.transitioned {
|
if e.handled || e.transitioned {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Универсальный обработчик состояния (если ничего не подошло)
|
// 3. Универсальный обработчик состояния (если ничего не подошло)
|
||||||
b.CurrentState.Handle(b, u)
|
b.CurrentState.Handle(b, u)
|
||||||
if exec.handled || exec.transitioned {
|
if e.handled || e.transitioned {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,7 +348,7 @@ func (b *Bot) Update(u *echotron.Update) {
|
|||||||
mode = EditMessage
|
mode = EditMessage
|
||||||
}
|
}
|
||||||
b.CurrentState.Enter(b, mode)
|
b.CurrentState.Enter(b, mode)
|
||||||
if exec.handled || exec.transitioned {
|
if e.handled || e.transitioned {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
196
tg_bot/bot/forward_system_event.go
Normal file
196
tg_bot/bot/forward_system_event.go
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/NicoNex/echotron/v3"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *Bot) forwardSystemEvent(u *echotron.Update) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case u.ChatJoinRequest != nil:
|
||||||
|
req := u.ChatJoinRequest
|
||||||
|
if !isChannelChat(req.Chat.Type) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteLink := ""
|
||||||
|
if req.InviteLink != nil {
|
||||||
|
inviteLink = req.InviteLink.InviteLink
|
||||||
|
}
|
||||||
|
if inviteLink == "" {
|
||||||
|
log.Debug().Msg("No invite link in chat join request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := req.From.ID
|
||||||
|
username := stringPtr(req.From.Username)
|
||||||
|
firstName := stringPtr(req.From.FirstName)
|
||||||
|
lastName := stringPtr(req.From.LastName)
|
||||||
|
if userID == 0 && req.UserChatID != 0 {
|
||||||
|
userID = req.UserChatID
|
||||||
|
}
|
||||||
|
if userID == 0 {
|
||||||
|
log.Error().Msg("No user id in chat join request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := map[string]any{
|
||||||
|
"type": "subscription",
|
||||||
|
"user_telegram_id": userID,
|
||||||
|
"invite_link": inviteLink,
|
||||||
|
"username": username,
|
||||||
|
"first_name": firstName,
|
||||||
|
"last_name": lastName,
|
||||||
|
}
|
||||||
|
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to send subscription event")
|
||||||
|
}
|
||||||
|
|
||||||
|
case u.ChatMember != nil:
|
||||||
|
event := u.ChatMember
|
||||||
|
if !isChannelChat(event.Chat.Type) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
memberUser := event.NewChatMember.User
|
||||||
|
if memberUser == nil {
|
||||||
|
log.Error().Msg("No user in chat member update")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oldStatus := event.OldChatMember.Status
|
||||||
|
newStatus := event.NewChatMember.Status
|
||||||
|
|
||||||
|
wasNotMember := oldStatus == "left" || oldStatus == "kicked"
|
||||||
|
isNowMember := newStatus == "member" || newStatus == "administrator" || newStatus == "creator"
|
||||||
|
userJoined := wasNotMember && isNowMember
|
||||||
|
|
||||||
|
wasMember := oldStatus == "member" || oldStatus == "administrator" || oldStatus == "creator" || oldStatus == "restricted"
|
||||||
|
isNowNotMember := newStatus == "left" || newStatus == "kicked"
|
||||||
|
userLeft := wasMember && isNowNotMember
|
||||||
|
|
||||||
|
if userJoined {
|
||||||
|
inviteLink := ""
|
||||||
|
if event.InviteLink != nil {
|
||||||
|
inviteLink = event.InviteLink.InviteLink
|
||||||
|
}
|
||||||
|
if inviteLink == "" {
|
||||||
|
log.Debug().Msg("No invite link in chat member update")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := stringPtr(memberUser.Username)
|
||||||
|
firstName := stringPtr(memberUser.FirstName)
|
||||||
|
lastName := stringPtr(memberUser.LastName)
|
||||||
|
payload := map[string]any{
|
||||||
|
"type": "subscription",
|
||||||
|
"user_telegram_id": memberUser.ID,
|
||||||
|
"invite_link": inviteLink,
|
||||||
|
"username": username,
|
||||||
|
"first_name": firstName,
|
||||||
|
"last_name": lastName,
|
||||||
|
}
|
||||||
|
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to send subscription event")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if userLeft {
|
||||||
|
payload := map[string]any{
|
||||||
|
"type": "unsubscription",
|
||||||
|
"user_telegram_id": memberUser.ID,
|
||||||
|
"channel_telegram_id": event.Chat.ID,
|
||||||
|
}
|
||||||
|
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to send unsubscription event")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case u.MyChatMember != nil:
|
||||||
|
event := u.MyChatMember
|
||||||
|
if !isChannelChat(event.Chat.Type) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
actorID := event.From.ID
|
||||||
|
if actorID == 0 {
|
||||||
|
log.Error().Msg("No user in my chat member update")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oldStatus := event.OldChatMember.Status
|
||||||
|
newStatus := event.NewChatMember.Status
|
||||||
|
|
||||||
|
wasMember := oldStatus == "administrator" || oldStatus == "member"
|
||||||
|
isNowNotMember := newStatus == "left" || newStatus == "kicked"
|
||||||
|
botRemoved := wasMember && isNowNotMember
|
||||||
|
|
||||||
|
permissionsChanged := oldStatus == "administrator" && newStatus == "administrator"
|
||||||
|
|
||||||
|
if botRemoved {
|
||||||
|
payload := map[string]any{
|
||||||
|
"type": "bot_removed",
|
||||||
|
"telegram_id": event.Chat.ID,
|
||||||
|
"user_telegram_id": actorID,
|
||||||
|
}
|
||||||
|
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to send bot removed event")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if permissionsChanged {
|
||||||
|
title := event.Chat.Title
|
||||||
|
if title == "" {
|
||||||
|
title = fmt.Sprintf("Channel %d", event.Chat.ID)
|
||||||
|
}
|
||||||
|
payload := map[string]any{
|
||||||
|
"type": "bot_permissions",
|
||||||
|
"telegram_id": event.Chat.ID,
|
||||||
|
"chat_title": title,
|
||||||
|
"user_telegram_id": actorID,
|
||||||
|
"permissions": map[string]any{
|
||||||
|
"is_admin": newStatus == "administrator",
|
||||||
|
"can_invite_users": event.NewChatMember.CanInviteUsers,
|
||||||
|
"can_restrict_members": event.NewChatMember.CanRestrictMembers,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to send bot permissions event")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wasNotMember := oldStatus == "left" || oldStatus == "kicked"
|
||||||
|
isNowMember := newStatus == "administrator" || newStatus == "member"
|
||||||
|
botAdded := wasNotMember && isNowMember
|
||||||
|
|
||||||
|
if botAdded {
|
||||||
|
title := event.Chat.Title
|
||||||
|
if title == "" {
|
||||||
|
title = fmt.Sprintf("Channel %d", event.Chat.ID)
|
||||||
|
}
|
||||||
|
payload := map[string]any{
|
||||||
|
"type": "bot_added",
|
||||||
|
"telegram_id": event.Chat.ID,
|
||||||
|
"title": title,
|
||||||
|
"username": stringPtr(event.Chat.Username),
|
||||||
|
"user_telegram_id": actorID,
|
||||||
|
"bot_permissions": map[string]any{
|
||||||
|
"is_admin": newStatus == "administrator",
|
||||||
|
"can_invite_users": event.NewChatMember.CanInviteUsers,
|
||||||
|
"can_restrict_members": event.NewChatMember.CanRestrictMembers,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := b.Backend.SendEvent(ctx, payload); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to send bot added event")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user