Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove bot message in main group after admin action #171

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions app/events/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@
)

// ReportBan a ban message to admin chat with a button to unban the user
func (a *admin) ReportBan(banUserStr string, msg *bot.Message) {
func (a *admin) ReportBan(banUserStr string, msg *bot.Message, spamReplyID int) {
log.Printf("[DEBUG] report to admin chat, ban msgsData for %s, group: %d", banUserStr, a.adminChatID)
text := strings.ReplaceAll(escapeMarkDownV1Text(msg.Text), "\n", " ")
would := ""
if a.dry {
would = "would have "
}
forwardMsg := fmt.Sprintf("**%spermanently banned [%s](tg://user?id=%d)**\n\n%s\n\n", would, banUserStr, msg.From.ID, text)
if err := a.sendWithUnbanMarkup(forwardMsg, "change ban", msg.From, msg.ID, a.adminChatID); err != nil {
if err := a.sendWithUnbanMarkup(forwardMsg, "change ban", msg.From, msg.ID, a.adminChatID, spamReplyID); err != nil {
log.Printf("[WARN] failed to send admin message, %v", err)
}
}
Expand Down Expand Up @@ -381,7 +381,7 @@
return fmt.Errorf("failed to update spam for %q: %w", cleanMsg, err)
}

userID, msgID, parseErr := a.parseCallbackData(query.Data)
userID, msgID, spamReplyID, parseErr := a.parseCallbackData(query.Data)
if parseErr != nil {
return fmt.Errorf("failed to parse callback's userID %q: %w", query.Data, parseErr)
}
Expand All @@ -395,6 +395,11 @@

// for soft ban we need to ba user for real on confirmation
if a.softBan && !a.trainingMode {
// we can delete our reply to the chat
deleteMsg := tbapi.NewDeleteMessage(a.primChatID, spamReplyID)
if err := send(deleteMsg, a.tbAPI); err != nil {
log.Printf("[WARN] failed to delete our spam reply message %d: %v", spamReplyID, err)
}
userName, err := a.extractUsername(query.Message.Text) // try to extract username from the message
if err != nil {
log.Printf("[DEBUG] failed to extract username from %q: %v", query.Message.Text, err)
Expand Down Expand Up @@ -424,7 +429,7 @@
return fmt.Errorf("failed to send callback response: %w", err)
}

userID, _, err := a.parseCallbackData(callbackData)
userID, _, spamReplyID, err := a.parseCallbackData(callbackData)
if err != nil {
return fmt.Errorf("failed to parse callback msgsData %q: %w", callbackData, err)
}
Expand Down Expand Up @@ -479,6 +484,14 @@
if err := send(editMsg, a.tbAPI); err != nil {
return fmt.Errorf("failed to edit message, chatID:%d, msgID:%d, %w", chatID, query.Message.MessageID, err)
}

// we can delete our reply to the chat
if spamReplyID != 0 {
deleteMsg := tbapi.NewDeleteMessage(a.primChatID, spamReplyID)
if err := send(deleteMsg, a.tbAPI); err != nil {
log.Printf("[WARN] failed to delete our spam reply message %d: %v", spamReplyID, err)
}
}
return nil
}

Expand Down Expand Up @@ -519,7 +532,7 @@
callbackData := query.Data
spamInfoText := "**can't get spam info**"
spamInfo := []string{}
userID, _, err := a.parseCallbackData(callbackData)
userID, _, _, err := a.parseCallbackData(callbackData)
if err != nil {
spamInfo = append(spamInfo, fmt.Sprintf("**failed to parse userID from %q: %v**", callbackData[1:], err))
}
Expand Down Expand Up @@ -633,7 +646,7 @@
// sendWithUnbanMarkup sends a message to admin chat and adds buttons to ui.
// text is message with details and action it for the button label to unban, which is user id prefixed with "?" for confirmation;
// the second button is to show info about the spam analysis.
func (a *admin) sendWithUnbanMarkup(text, action string, user bot.User, msgID int, chatID int64) error {
func (a *admin) sendWithUnbanMarkup(text, action string, user bot.User, msgID int, chatID int64, spamReplyID int) error {
log.Printf("[DEBUG] action response %q: user %+v, msgID:%d, text: %q", action, user, msgID, strings.ReplaceAll(text, "\n", "\\n"))
tbMsg := tbapi.NewMessage(chatID, text)
tbMsg.ParseMode = tbapi.ModeMarkdown
Expand All @@ -642,9 +655,9 @@
tbMsg.ReplyMarkup = tbapi.NewInlineKeyboardMarkup(
tbapi.NewInlineKeyboardRow(
// ?userID to request confirmation
tbapi.NewInlineKeyboardButtonData("⛔︎ "+action, fmt.Sprintf("%s%d:%d", confirmationPrefix, user.ID, msgID)),
tbapi.NewInlineKeyboardButtonData("⛔︎ "+action, fmt.Sprintf("%s%d:%d:%d", confirmationPrefix, user.ID, msgID, spamReplyID)),
// !userID to request info
tbapi.NewInlineKeyboardButtonData("️⚑ info", fmt.Sprintf("%s%d:%d", infoPrefix, user.ID, msgID)),
tbapi.NewInlineKeyboardButtonData("️⚑ info", fmt.Sprintf("%s%d:%d:%d", infoPrefix, user.ID, msgID, spamReplyID)),
),
)

Expand All @@ -654,10 +667,10 @@
return nil
}

// callbackData is a string with userID and msgID separated by ":"
func (a *admin) parseCallbackData(data string) (userID int64, msgID int, err error) {
if len(data) < 3 {
return 0, 0, fmt.Errorf("unexpected callback data, too short %q", data)
// callbackData is a string with userID, msgID and spamReplyID separated by ":"
func (a *admin) parseCallbackData(data string) (userID int64, msgID int, spamReplyID int, err error) {

Check failure on line 671 in app/events/admin.go

View workflow job for this annotation

GitHub Actions / build

paramTypeCombine: func(data string) (userID int64, msgID int, spamReplyID int, err error) could be replaced with func(data string) (userID int64, msgID, spamReplyID int, err error) (gocritic)
if len(data) < 4 {
return 0, 0, 0, fmt.Errorf("unexpected callback data, too short %q", data)
}

// remove prefix if present from the parsed data
Expand All @@ -666,17 +679,21 @@
}

parts := strings.Split(data, ":")
if len(parts) != 2 {
return 0, 0, fmt.Errorf("unexpected callback data, should have both ids %q", data)
if len(parts) != 3 {
return 0, 0, 0, fmt.Errorf("unexpected callback data, should have 3 ids %q", data)
}
if userID, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
return 0, 0, fmt.Errorf("failed to parse userID %q: %w", parts[0], err)
return 0, 0, 0, fmt.Errorf("failed to parse userID %q: %w", parts[0], err)
}
if msgID, err = strconv.Atoi(parts[1]); err != nil {
return 0, 0, fmt.Errorf("failed to parse msgID %q: %w", parts[1], err)
return 0, 0, 0, fmt.Errorf("failed to parse msgID %q: %w", parts[1], err)
}

if spamReplyID, err = strconv.Atoi(parts[2]); err != nil {
return 0, 0, 0, fmt.Errorf("failed to parse spamReplyID %q: %w", parts[2], err)
}

return userID, msgID, nil
return userID, msgID, spamReplyID, nil
}

// extractUsername tries to extract the username from a ban message
Expand Down
28 changes: 15 additions & 13 deletions app/events/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ func TestAdmin_reportBan(t *testing.T) {
},
Text: "Test\n\n_message_",
}

adm.ReportBan("testUser", msg)
spamReplyID := 789
adm.ReportBan("testUser", msg, spamReplyID)

require.Equal(t, 1, len(mockAPI.SendCalls()))
t.Logf("sent text: %+v", mockAPI.SendCalls()[0].C.(tbapi.MessageConfig).Text)
Expand Down Expand Up @@ -128,29 +128,31 @@ func TestAdmin_parseCallbackData(t *testing.T) {
data string
wantUserID int64
wantMsgID int
wantSpamReplyID int
wantErr bool
}{
{"Valid data", "12345:678", 12345, 678, false},
{"Data too short", "12", 0, 0, true},
{"No colon separator", "12345678", 0, 0, true},
{"Invalid userID", "abc:678", 0, 0, true},
{"Invalid msgID", "12345:xyz", 0, 0, true},
{"wrong prefix with valid data", "c12345:678", 0, 0, true},
{"valid prefix+ with valid data", "+12345:678", 12345, 678, false},
{"valid prefix! with valid data", "!12345:678", 12345, 678, false},
{"valid prefix? with valid data", "?12345:678", 12345, 678, false},
{"Valid data", "12345:678:90", 12345, 678, 90, false},
{"Data too short", "12", 0, 0, 0, true},
{"No colon separator", "12345678", 0, 0, 0, true},
{"Invalid userID", "abc:678", 0, 0, 0, true},
{"Invalid msgID", "12345:xyz", 0, 0, 0, true},
{"wrong prefix with valid data", "c12345:678", 0, 0, 0, true},
{"valid prefix+ with valid data", "+12345:678:90", 12345, 678, 90, false},
{"valid prefix! with valid data", "!12345:678:90", 12345, 678, 90, false},
{"valid prefix? with valid data", "?12345:678:90", 12345, 678, 90, false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := admin{}
gotUserID, gotMsgID, err := a.parseCallbackData(tt.data)
gotUserID, gotMsgID, spamReplyID, err := a.parseCallbackData(tt.data)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.wantUserID, gotUserID)
assert.Equal(t, tt.wantMsgID, gotMsgID)
assert.Equal(t, tt.wantSpamReplyID, spamReplyID)
}
})
}
Expand Down Expand Up @@ -208,7 +210,7 @@ func TestAdmin_dryModeForwardMessage(t *testing.T) {
}
msg := &bot.Message{}

adm.ReportBan("testUser", msg)
adm.ReportBan("testUser", msg, 789)
assert.Contains(t, mockAPI.SendCalls()[0].C.(tbapi.MessageConfig).Text, "would have permanently banned [testUser]")

}
29 changes: 16 additions & 13 deletions app/events/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (l *TelegramListener) Do(ctx context.Context) error {

// send startup message if any set
if l.StartupMsg != "" && !l.TrainingMode && !l.Dry {
if err := l.sendBotResponse(bot.Response{Send: true, Text: l.StartupMsg}, l.chatID); err != nil {
if _, err := l.sendBotResponse(bot.Response{Send: true, Text: l.StartupMsg}, l.chatID); err != nil {
log.Printf("[WARN] failed to send startup message, %v", err)
}
}
Expand Down Expand Up @@ -129,7 +129,7 @@ func (l *TelegramListener) Do(ctx context.Context) error {
}
if err := l.adminHandler.MsgHandler(update); err != nil {
log.Printf("[WARN] failed to process admin chat message: %v", err)
_ = l.sendBotResponse(bot.Response{Send: true, Text: "error: " + err.Error()}, l.adminChatID)
_, _ = l.sendBotResponse(bot.Response{Send: true, Text: "error: " + err.Error()}, l.adminChatID)
}
continue
}
Expand All @@ -138,7 +138,7 @@ func (l *TelegramListener) Do(ctx context.Context) error {
if update.CallbackQuery != nil {
if err := l.adminHandler.InlineCallbackHandler(update.CallbackQuery); err != nil {
log.Printf("[WARN] failed to process callback: %v", err)
_ = l.sendBotResponse(bot.Response{Send: true, Text: "error: " + err.Error()}, l.adminChatID)
_, _ = l.sendBotResponse(bot.Response{Send: true, Text: "error: " + err.Error()}, l.adminChatID)
}
continue
}
Expand Down Expand Up @@ -202,7 +202,7 @@ func (l *TelegramListener) Do(ctx context.Context) error {

case <-time.After(l.IdleDuration): // hit bots on idle timeout
resp := l.Bot.OnMessage(bot.Message{Text: "idle"})
if err := l.sendBotResponse(resp, l.chatID); err != nil {
if _, err := l.sendBotResponse(resp, l.chatID); err != nil {
log.Printf("[WARN] failed to respond on idle, %v", err)
}
}
Expand Down Expand Up @@ -288,10 +288,13 @@ func (l *TelegramListener) procEvents(update tbapi.Update) error {
}

// send response to the channel if allowed
spamReplyID := 0
if resp.Send && !l.NoSpamReply && !l.TrainingMode {
if err := l.sendBotResponse(resp, fromChat); err != nil {
var err error
if spamReplyID, err = l.sendBotResponse(resp, fromChat); err != nil {
log.Printf("[WARN] failed to respond on update, %v", err)
}
log.Printf("[DEBUG] response sent, spamReplyID: %d", spamReplyID)
}

errs := new(multierror.Error)
Expand All @@ -307,7 +310,7 @@ func (l *TelegramListener) procEvents(update tbapi.Update) error {

if l.SuperUsers.IsSuper(msg.From.Username, msg.From.ID) {
if l.TrainingMode {
l.adminHandler.ReportBan(banUserStr, msg)
l.adminHandler.ReportBan(banUserStr, msg, 0)
}
log.Printf("[DEBUG] superuser %s requested ban, ignored", banUserStr)
return nil
Expand All @@ -318,7 +321,7 @@ func (l *TelegramListener) procEvents(update tbapi.Update) error {
if err := banUserOrChannel(banReq); err != nil {
errs = multierror.Append(errs, fmt.Errorf("failed to ban %s: %w", banUserStr, err))
} else if l.adminChatID != 0 && msg.From.ID != 0 {
l.adminHandler.ReportBan(banUserStr, msg)
l.adminHandler.ReportBan(banUserStr, msg, spamReplyID)
}
}

Expand Down Expand Up @@ -375,9 +378,9 @@ func (l *TelegramListener) getBanUsername(resp bot.Response, update tbapi.Update

// sendBotResponse sends bot's answer to tg channel
// actionText is a text for the button to unban user, optional
func (l *TelegramListener) sendBotResponse(resp bot.Response, chatID int64) error {
func (l *TelegramListener) sendBotResponse(resp bot.Response, chatID int64) (int, error) {
if !resp.Send {
return nil
return 0, nil
}

log.Printf("[DEBUG] bot response - %+v, reply-to:%d", strings.ReplaceAll(resp.Text, "\n", "\\n"), resp.ReplyTo)
Expand All @@ -386,11 +389,11 @@ func (l *TelegramListener) sendBotResponse(resp bot.Response, chatID int64) erro
tbMsg.DisableWebPagePreview = true
tbMsg.ReplyToMessageID = resp.ReplyTo

if err := send(tbMsg, l.TbAPI); err != nil {
return fmt.Errorf("can't send message to telegram %q: %w", resp.Text, err)
tbResp, err := l.TbAPI.Send(tbMsg)
if err != nil {
return 0, fmt.Errorf("can't send message to telegram %q: %w", resp.Text, err)
}

return nil
return tbResp.MessageID, nil
}

func (l *TelegramListener) getChatID(group string) (int64, error) {
Expand Down
23 changes: 14 additions & 9 deletions app/events/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ func TestTelegramListener_DoWithAdminUnBan(t *testing.T) {

updMsg := tbapi.Update{
CallbackQuery: &tbapi.CallbackQuery{
Data: "777:999",
Data: "777:999:88",
Message: &tbapi.Message{
MessageID: 987654,
Chat: &tbapi.Chat{ID: 123},
Expand All @@ -772,9 +772,12 @@ func TestTelegramListener_DoWithAdminUnBan(t *testing.T) {

err := l.Do(ctx)
assert.EqualError(t, err, "telegram update chan closed")
require.Equal(t, 1, len(mockAPI.SendCalls()))
require.Equal(t, 2, len(mockAPI.SendCalls()))
assert.Equal(t, 987654, mockAPI.SendCalls()[0].C.(tbapi.EditMessageTextConfig).MessageID)
assert.Contains(t, mockAPI.SendCalls()[0].C.(tbapi.EditMessageTextConfig).Text, "by admin in ")

assert.Equal(t, 88, mockAPI.SendCalls()[1].C.(tbapi.DeleteMessageConfig).MessageID)

require.Equal(t, 2, len(mockAPI.RequestCalls()))
assert.Equal(t, "accepted", mockAPI.RequestCalls()[0].C.(tbapi.CallbackConfig).Text)

Expand Down Expand Up @@ -828,7 +831,7 @@ func TestTelegramListener_DoWithAdminSoftUnBan(t *testing.T) {

updMsg := tbapi.Update{
CallbackQuery: &tbapi.CallbackQuery{
Data: "777:999",
Data: "777:999:88",
Message: &tbapi.Message{
MessageID: 987654,
Chat: &tbapi.Chat{ID: 123},
Expand All @@ -846,9 +849,10 @@ func TestTelegramListener_DoWithAdminSoftUnBan(t *testing.T) {

err := l.Do(ctx)
assert.EqualError(t, err, "telegram update chan closed")
require.Equal(t, 1, len(mockAPI.SendCalls()))
require.Equal(t, 2, len(mockAPI.SendCalls()))
assert.Equal(t, 987654, mockAPI.SendCalls()[0].C.(tbapi.EditMessageTextConfig).MessageID)
assert.Contains(t, mockAPI.SendCalls()[0].C.(tbapi.EditMessageTextConfig).Text, "by admin in ")
assert.Equal(t, 88, mockAPI.SendCalls()[1].C.(tbapi.DeleteMessageConfig).MessageID)
require.Equal(t, 2, len(mockAPI.RequestCalls()))
assert.Equal(t, "accepted", mockAPI.RequestCalls()[0].C.(tbapi.CallbackConfig).Text)

Expand Down Expand Up @@ -905,7 +909,7 @@ func TestTelegramListener_DoWithAdminUnBan_Training(t *testing.T) {

updMsg := tbapi.Update{
CallbackQuery: &tbapi.CallbackQuery{
Data: "777:999",
Data: "777:999:88",
Message: &tbapi.Message{
MessageID: 987654,
Chat: &tbapi.Chat{ID: 123},
Expand All @@ -923,9 +927,10 @@ func TestTelegramListener_DoWithAdminUnBan_Training(t *testing.T) {

err := l.Do(ctx)
assert.EqualError(t, err, "telegram update chan closed")
require.Equal(t, 1, len(mockAPI.SendCalls()))
require.Equal(t, 2, len(mockAPI.SendCalls()))
assert.Equal(t, 987654, mockAPI.SendCalls()[0].C.(tbapi.EditMessageTextConfig).MessageID)
assert.Contains(t, mockAPI.SendCalls()[0].C.(tbapi.EditMessageTextConfig).Text, "by admin in ")
assert.Equal(t, 88, mockAPI.SendCalls()[1].C.(tbapi.DeleteMessageConfig).MessageID)
require.Equal(t, 1, len(mockAPI.RequestCalls()))
assert.Equal(t, "accepted", mockAPI.RequestCalls()[0].C.(tbapi.CallbackConfig).Text)
require.Equal(t, 1, len(b.UpdateHamCalls()))
Expand Down Expand Up @@ -1045,7 +1050,7 @@ func TestTelegramListener_DoWithAdminUnbanDecline(t *testing.T) {

updMsg := tbapi.Update{
CallbackQuery: &tbapi.CallbackQuery{
Data: "+999:987654", // + means unban declined
Data: "+999:987654:88", // + means unban declined
Message: &tbapi.Message{
MessageID: 987654,
Chat: &tbapi.Chat{ID: 123},
Expand Down Expand Up @@ -1117,7 +1122,7 @@ func TestTelegramListener_DoWithAdminBanConfirmedTraining(t *testing.T) {

updMsg := tbapi.Update{
CallbackQuery: &tbapi.CallbackQuery{
Data: "+999:987654", // + means unban declined
Data: "+999:987654:88", // + means unban declined
Message: &tbapi.Message{
MessageID: 987654,
Chat: &tbapi.Chat{ID: 123},
Expand Down Expand Up @@ -1187,7 +1192,7 @@ func TestTelegramListener_DoWithAdminShowInfo(t *testing.T) {

updMsg := tbapi.Update{
CallbackQuery: &tbapi.CallbackQuery{
Data: "!999:987654", // ! means we show info
Data: "!999:987654:88", // ! means we show info
Message: &tbapi.Message{
MessageID: 987654,
Chat: &tbapi.Chat{ID: 123},
Expand Down
Loading