func (b *bans) deleteMessage(bot tgbotInterface, admin *tgbotapi.User, requestID string) error {
	b.Lock()
	defer b.Unlock()
	report, ok := b.Requests.Bans[requestID]
	if !ok {
		return fmt.Errorf("request id %q not found", requestID)
	}
	if report.MessageRemoved {
		return fmt.Errorf("message indicated by request %q already handled", requestID)
	}
	_, err := bot.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: report.ChatID, MessageID: int(report.MessageID)})
	if err != nil {
		return err
	}
	report.MessageRemoved = true
	report.RemovedBy = int64(admin.ID)
	b.Requests.Bans[requestID] = report
	return safeWriteJSON(b.Requests, b.requestedBansDB)
}
deleteMessage deletes the message indicated by requestID and updates the
information on disk relative to it.
package main
import (
	"fmt"
	"github.com/go-telegram-bot-api/telegram-bot-api"
	"log"
	"strings"
	"sync"
)
const (
	requestedBansDB = "bans.json"
	adminNotificationDefaultThreshold = 1
	parseModeMarkdown = "Markdown"
)
type banRequest struct {
	MessageID int64 `json:"message"`
	ChatID int64 `json:"chat"`
	Author int64 `json:"author"`
	Text string `json:"text"`
	Reporters map[int64]int64 `json:"reporters"`
	Notifications map[int64]int64 `json:"notifications"`
	MessageRemoved bool `json:"removed"`
	RemovedBy int64 `json:"handler"`
}
type banRequestList struct {
	NotificationThreshold int `json:"threshold"`
	Bans map[string]banRequest `json:"bans"`
}
type bans struct {
	sync.RWMutex
	Requests        banRequestList
	requestedBansDB string
}
func newBans() *bans {
	return &bans{
		Requests: banRequestList{
			NotificationThreshold: adminNotificationDefaultThreshold,
			Bans:                  map[string]banRequest{},
		},
		requestedBansDB: requestedBansDB,
	}
}
func (b *bans) banRequestHandler(bot tgbotInterface, update tgbotapi.Update) error {
	if update.Message == nil || update.Message.Chat == nil || isPrivateChat(update.Message.Chat) {
		log.Printf("banRequestHandler: either i) the message or chat was nil or ii) this command was issued in a private message")
		return nil
	}
	if update.Message.ReplyToMessage == nil {
		log.Printf("banRequestHandler: /ban called by %q without being in response to a message; ignoring.", update.Message.From)
		return nil
	}
	offendingMessageID := update.Message.ReplyToMessage.MessageID
	b.Lock()
	defer b.Unlock()
	key := fmt.Sprintf("%d:%d", offendingMessageID, update.Message.Chat.ID)
	if _, ok := b.Requests.Bans[key]; ok {
		if b.Requests.Bans[key].MessageRemoved {
			log.Printf("banRequestHandler: /ban called by %q on message %q, but it was already removed; ignoring.", update.Message.From, offendingMessageID)
			return nil
		}
	} else {
		report := banRequest{
			Author:         int64(update.Message.ReplyToMessage.From.ID),
			ChatID:         update.Message.Chat.ID,
			MessageID:      int64(offendingMessageID),
			MessageRemoved: false,
			Notifications:  map[int64]int64{},
			RemovedBy:      0,
			Reporters:      map[int64]int64{},
		}
		b.Requests.Bans[key] = report
		if err := safeWriteJSON(b.Requests, b.requestedBansDB); err != nil {
			log.Printf("banRequestHandler: problem updating file %q after adding new request %v: %v", requestedBansDB, report, err)
			return nil
		}
	}
	b.Requests.Bans[key].Reporters[int64(update.Message.From.ID)] = int64(update.Message.MessageID)
	if err := safeWriteJSON(b.Requests, b.requestedBansDB); err != nil {
		log.Printf("banRequestHandler: problem updating the file %q after updating list of ban reporters for message %q in %q: %v",
			requestedBansDB, b.Requests.Bans[key].MessageID, b.Requests.Bans[key].ChatID, err)
		return nil
	}
	if len(b.Requests.Bans[key].Notifications) == 0 && len(b.Requests.Bans[key].Reporters) >= b.Requests.NotificationThreshold {
		chatConfig := tgbotapi.ChatConfig{ChatID: update.Message.Chat.ID}
		admins, err := bot.GetChatAdministrators(chatConfig)
		if err != nil {
			log.Printf("banRequestHandler: problem trying to get chat administrators: %v", err)
			return nil
		}
		for _, admin := range admins {
			msgid, err := notifyAdmin(bot, admin.User, update)
			if err != nil {
				log.Printf("banRequestHandler: problem notifying admin %q (uid: %q): %v", formatName(*admin.User), admin.User.ID, err)
				continue
			}
			b.Requests.Bans[key].Notifications[int64(admin.User.ID)] = msgid
		}
		if len(b.Requests.Bans[key].Notifications) > 0 {
			report := b.Requests.Bans[key]
			report.Text = update.Message.ReplyToMessage.Text
			b.Requests.Bans[key] = report
			if err := safeWriteJSON(b.Requests, requestedBansDB); err != nil {
				log.Printf("banRequestHandler: problem updating file %q after sending notifications to admins regarding offending message %q in %q: %v",
					requestedBansDB, b.Requests.Bans[key].MessageID, b.Requests.Bans[key].ChatID, err)
				return nil
			}
		}
	}
	return nil
}
func (b *bans) loadBanRequestsInfo() error {
	b.Lock()
	defer b.Unlock()
	return readJSONFromDataDir(&b.Requests, b.requestedBansDB)
}
func notifyAdmin(bot tgbotInterface, admin *tgbotapi.User, update tgbotapi.Update) (int64, error) {
	offendingMessageID := update.Message.ReplyToMessage.MessageID
	chatID := update.Message.Chat.ID
	requestID := fmt.Sprintf("%d:%d", offendingMessageID, chatID)
	removeMessageButton := button(T("remove_message"), fmt.Sprintf("delete-message-%s", requestID))
	removeMessageAndBanUserButton := button(T("remove_message_and_ban"), fmt.Sprintf("ban-user-%s", requestID))
	var markup tgbotapi.InlineKeyboardMarkup
	if len(update.Message.Chat.UserName) > 0 {
		goToMessageButton := buttonURL(T("go_to_notification"), fmt.Sprintf("https://t.me/%s/%d", update.Message.Chat.UserName, update.Message.ReplyToMessage.MessageID))
		markup = tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(goToMessageButton),
			tgbotapi.NewInlineKeyboardRow(removeMessageButton, removeMessageAndBanUserButton),
		)
	} else {
		markup = tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(removeMessageButton, removeMessageAndBanUserButton),
		)
	}
	notificationText := fmt.Sprintf(T("notify_admin"), formatName(*update.Message.From), update.Message.From.ID, update.Message.Chat.Title, update.Message.ReplyToMessage.Text)
	notificationText = strings.Replace(notificationText, `\n`, "\n", -1)
	msg := tgbotapi.NewMessage(int64(admin.ID), notificationText)
	msg.ParseMode = parseModeMarkdown
	msg.ReplyMarkup = &markup
	mediaMsg, ok, err := createMediaMessage(update.Message.ReplyToMessage, int64(admin.ID), nil)
	if err != nil {
		return 0, err
	}
	sentMessage, err := bot.Send(msg)
	if err != nil {
		return 0, err
	}
	if ok {
		bot.Send(mediaMsg)
	}
	return int64(sentMessage.MessageID), nil
}
func (b *bans) deleteMessageFromBanRequest(bot tgbotInterface, admin *tgbotapi.User, requestID string, ban bool) error {
	err := b.deleteMessage(bot, admin, requestID)
	if err != nil {
		return err
	}
	b.RLock()
	defer b.RUnlock()
	if !ban {
		return b.updateBanRequestNotification(bot, requestID, admin, T("notification_update_delete"))
	}
	if err := banUser(bot, b.Requests.Bans[requestID].ChatID, int(b.Requests.Bans[requestID].Author)); err != nil {
		return err
	}
	return b.updateBanRequestNotification(bot, requestID, admin, T("notification_update_delete_and_ban"))
}