470 lines
15 KiB
Go
470 lines
15 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/db/model"
|
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/iface"
|
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/store"
|
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/util"
|
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration"
|
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/locale"
|
|
"github.com/mymmrac/telego"
|
|
tu "github.com/mymmrac/telego/telegoutil"
|
|
"strings"
|
|
)
|
|
|
|
type CallbackQueryHandler struct {
|
|
iface.Base
|
|
}
|
|
|
|
func NewCallbackQueryHandler(app iface.App) Handler {
|
|
return &CallbackQueryHandler{iface.NewBase(app, 0, 0)}
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) Handle(wh telego.Update) error {
|
|
cq := wh.CallbackQuery
|
|
if cq.Message.GetChat().Type == telego.ChatTypePrivate {
|
|
user, err := h.App.DB().ForUser().ByTelegramID(cq.From.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if user != nil && user.ID > 0 {
|
|
return h.handleSetupKey(cq, user)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var pl util.Payload
|
|
if err := json.Unmarshal([]byte(cq.Data), &pl); err != nil {
|
|
return err
|
|
}
|
|
chat, err := h.App.DB().ForChat().ByTelegramID(pl.Chat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
user, err := h.App.DB().ForUser().ByID(chat.UserID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
loc := h.Localizer(user.Language)
|
|
switch pl.Action {
|
|
case util.PayloadActionVote:
|
|
poll, found, err := h.findPollByMessage(cq.Message, loc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return nil
|
|
}
|
|
return h.handleVote(cq.From, chat, cq.Message, poll, pl, loc)
|
|
case util.PayloadActionVoteFinish:
|
|
poll, found, err := h.findPollByMessage(cq.Message, loc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return nil
|
|
}
|
|
return h.handleVoteFinish(cq.From, chat, cq.Message, poll, pl, loc)
|
|
case util.PayloadActionVoteSendRedmine:
|
|
poll, found, err := h.findPollByMessage(cq.Message, loc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return nil
|
|
}
|
|
return h.handleVoteSendRedmine(cq.From, chat, cq.Message, poll, pl, loc)
|
|
default:
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) handleVoteFinish(from telego.User, chat *model.Chat,
|
|
msg telego.MaybeInaccessibleMessage, poll store.PollState, pl util.Payload, loc locale.Localizer) error {
|
|
dotOrColon := "."
|
|
if poll.Subject != "" {
|
|
dotOrColon = ": "
|
|
}
|
|
if len(poll.Votes) == 0 {
|
|
store.Polls.Delete(msg.GetMessageID())
|
|
_, err := h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
|
ChatID: tu.ID(pl.Chat),
|
|
MessageID: msg.GetMessageID(),
|
|
Text: fmt.Sprintf("%s\n%s\n%s",
|
|
loc.Template("poker_start", map[string]interface{}{
|
|
"Name": poll.Initiator,
|
|
"DotOrColon": dotOrColon,
|
|
"Subject": poll.Subject,
|
|
}),
|
|
loc.Template("poker_ended", map[string]interface{}{"Name": from.Username}),
|
|
loc.Message("poker_ended_no_results"),
|
|
),
|
|
ParseMode: telego.ModeMarkdown,
|
|
})
|
|
h.App.Log().Debugf("finished poll: %#v", poll)
|
|
return err
|
|
}
|
|
|
|
poll.Calculate()
|
|
store.Polls.Set(msg.GetMessageID(), poll)
|
|
_, err := h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
|
ChatID: tu.ID(pl.Chat),
|
|
MessageID: msg.GetMessageID(),
|
|
Text: fmt.Sprintf("%s\n%s\n\n%s\n%s\n\n%s",
|
|
loc.Template("poker_start", map[string]interface{}{
|
|
"Name": poll.Initiator,
|
|
"DotOrColon": dotOrColon,
|
|
"Subject": poll.Subject,
|
|
}),
|
|
loc.Template("poker_ended", map[string]interface{}{"Name": from.Username}),
|
|
loc.Message("voted"),
|
|
h.voters(poll.Votes, loc),
|
|
loc.Template("poker_results", poll.Result),
|
|
),
|
|
ParseMode: telego.ModeMarkdown,
|
|
ReplyMarkup: h.getPollAfterEndKeyboard(chat, poll, loc),
|
|
})
|
|
h.App.Log().Debugf("finished poll: %#v", poll)
|
|
return err
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) handleVoteSendRedmine(from telego.User, chat *model.Chat,
|
|
msg telego.MaybeInaccessibleMessage, poll store.PollState, pl util.Payload, loc locale.Localizer) error {
|
|
dotOrColon := "."
|
|
if poll.Subject != "" {
|
|
dotOrColon = ": "
|
|
}
|
|
updateMessage := func(tag string) error {
|
|
_, err := h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
|
ChatID: tu.ID(pl.Chat),
|
|
MessageID: msg.GetMessageID(),
|
|
Text: fmt.Sprintf("%s\n%s\n\n%s\n%s\n\n%s\n\n%s",
|
|
loc.Template("poker_start", map[string]interface{}{
|
|
"Name": poll.Initiator,
|
|
"DotOrColon": dotOrColon,
|
|
"Subject": poll.Subject,
|
|
}),
|
|
loc.Template("poker_ended", map[string]interface{}{"Name": from.Username}),
|
|
loc.Message("voted"),
|
|
h.voters(poll.Votes, loc),
|
|
loc.Template("poker_results", poll.Result),
|
|
loc.Message(tag),
|
|
),
|
|
ParseMode: telego.ModeMarkdown,
|
|
})
|
|
return err
|
|
}
|
|
|
|
h.App.Log().Debugf("received vote send for poll: %#v", poll)
|
|
if !poll.CanRedmine || poll.TaskID == 0 {
|
|
store.Polls.Delete(msg.GetMessageID())
|
|
h.App.Log().Errorf("cannot send info to Redmine for %d: CanRedmine: %t", poll.TaskID, poll.CanRedmine)
|
|
return updateMessage("poker_redmine_cannot_send")
|
|
}
|
|
integrationData, err := h.App.DB().ForIntegration().LoadForChatAndType(chat.ID, model.RedmineIntegration)
|
|
if err != nil {
|
|
store.Polls.Delete(msg.GetMessageID())
|
|
h.App.Log().Errorf("cannot send info to Redmine for %d: %s", poll.TaskID, err)
|
|
return updateMessage("poker_redmine_cannot_send")
|
|
}
|
|
|
|
err = integration.New(*integrationData, h.App.Log()).PushVoteResult(poll.TaskID, poll.Result.RoundHalf)
|
|
if err != nil {
|
|
store.Polls.Delete(msg.GetMessageID())
|
|
h.App.Log().Errorf("cannot send info to Redmine for %d: %s", poll.TaskID, err)
|
|
return updateMessage("poker_redmine_cannot_send")
|
|
}
|
|
|
|
return updateMessage("poker_redmine_sent")
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) handleVote(from telego.User, chat *model.Chat,
|
|
msg telego.MaybeInaccessibleMessage, poll store.PollState, pl util.Payload, loc locale.Localizer) error {
|
|
var found bool
|
|
for i, storedVote := range poll.Votes {
|
|
if storedVote.ID == from.ID {
|
|
storedVote.Name = h.pollMemberName(&from)
|
|
storedVote.Vote = pl.Vote().Vote
|
|
poll.Votes[i] = storedVote
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
poll.Votes = append(poll.Votes, store.MemberVote{
|
|
Member: util.Member{
|
|
ID: from.ID,
|
|
Name: h.pollMemberName(&from),
|
|
},
|
|
Vote: pl.Vote().Vote,
|
|
})
|
|
}
|
|
store.Polls.Set(msg.GetMessageID(), poll)
|
|
|
|
h.App.Log().Debugf("processed vote %#v for poll: %#v", pl.Vote(), poll)
|
|
dotOrColon := "."
|
|
if poll.Subject != "" {
|
|
dotOrColon = ": "
|
|
}
|
|
_, err := h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
|
ChatID: tu.ID(pl.Chat),
|
|
MessageID: msg.GetMessageID(),
|
|
Text: fmt.Sprintf("%s\n%s\n%s",
|
|
loc.Template("poker_start", map[string]interface{}{
|
|
"Name": poll.Initiator,
|
|
"DotOrColon": dotOrColon,
|
|
"Subject": poll.Subject,
|
|
}),
|
|
loc.Message("voted"),
|
|
h.votersWithoutVotes(poll.Votes, loc),
|
|
),
|
|
ReplyMarkup: h.getPollKeyboard(chat, loc),
|
|
ParseMode: telego.ModeMarkdown,
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) pollMemberName(user *telego.User) string {
|
|
return fmt.Sprintf("%s (@%s)", strings.TrimRight(user.FirstName+" "+user.LastName, " "), user.Username)
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) voters(votes []store.MemberVote, loc locale.Localizer) string {
|
|
var sb strings.Builder
|
|
sb.Grow(20 * len(votes))
|
|
for _, vote := range votes {
|
|
sb.WriteString(loc.Template("vote_result", map[string]interface{}{
|
|
"Name": vote.Name,
|
|
"Vote": vote.Vote,
|
|
}) + "\n")
|
|
}
|
|
return strings.TrimRight(sb.String(), "\n")
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) votersWithoutVotes(votes []store.MemberVote, loc locale.Localizer) string {
|
|
var sb strings.Builder
|
|
sb.Grow(20 * len(votes))
|
|
for _, vote := range votes {
|
|
sb.WriteString(loc.Template("voter", map[string]interface{}{"Name": vote.Name}) + "\n")
|
|
}
|
|
return strings.TrimRight(sb.String(), "\n")
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) getPollKeyboard(chat *model.Chat, loc locale.Localizer) *telego.InlineKeyboardMarkup {
|
|
switch chat.KeyboardType {
|
|
case model.StandardKeyboard:
|
|
return util.StandardVoteKeyboard(chat.TelegramID, loc)
|
|
case model.StoryPointsKeyboard:
|
|
return util.StoryPointsVoteKeyboard(chat.TelegramID, loc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) getPollAfterEndKeyboard(chat *model.Chat, poll store.PollState, loc locale.Localizer) *telego.InlineKeyboardMarkup {
|
|
rm, err := h.App.DB().ForIntegration().LoadForChatAndType(chat.ID, model.RedmineIntegration)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if rm != nil && rm.ID > 0 && poll.CanRedmine && poll.TaskID > 0 {
|
|
return util.SendRedmineKeyboard(chat.TelegramID, poll.Result.RoundHalf, loc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) findPollByMessage(msg telego.MaybeInaccessibleMessage, loc locale.Localizer) (store.PollState, bool, error) {
|
|
poll, found := store.Polls.Get(msg.GetMessageID())
|
|
if !found {
|
|
_, err := h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
|
ChatID: tu.ID(msg.GetChat().ID),
|
|
MessageID: msg.GetMessageID(),
|
|
Text: loc.Message("cannot_find_poll"),
|
|
})
|
|
return store.PollState{}, false, err
|
|
}
|
|
return poll, true, nil
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) handleSetupKey(cq *telego.CallbackQuery, user *model.User) error {
|
|
var pl util.Payload
|
|
if err := json.Unmarshal([]byte(cq.Data), &pl); err != nil {
|
|
return err
|
|
}
|
|
switch pl.Action {
|
|
case util.PayloadActionChooseKeyboard:
|
|
return h.handleChooseKeyboard(pl, cq.Message.GetMessageID(), user)
|
|
case util.PayloadActionYesNo:
|
|
answer := pl.YesNoAnswer()
|
|
switch answer.Type {
|
|
case util.QuestionTypeRedmine:
|
|
return h.handleAnswerRedmine(answer, pl.Chat, cq.Message.GetMessageID(), user)
|
|
case util.QuestionTypeRedmineHours:
|
|
return h.handleAnswerRedmineHours(answer, pl.Chat, cq.Message.GetMessageID(), user)
|
|
case util.QuestionTypeRedmineSendResult:
|
|
return h.handleAnswerRedmineSendResults(answer, cq.Message.GetMessageID(), user)
|
|
}
|
|
default:
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) handleChooseKeyboard(pl util.Payload, msgID int, user *model.User) error {
|
|
cr := h.App.DB().ForChat()
|
|
result := pl.KeyboardChoice()
|
|
if pl.User != user.TelegramID {
|
|
return nil
|
|
}
|
|
chat, err := cr.ByTelegramID(pl.Chat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if chat == nil || chat.ID == 0 {
|
|
return nil
|
|
}
|
|
chat.KeyboardType = model.KeyboardType(result.Type)
|
|
if err := cr.Save(chat); err != nil {
|
|
return err
|
|
}
|
|
|
|
kbTypeName := "standard_vote_keyboard"
|
|
if model.KeyboardType(result.Type) == model.StoryPointsKeyboard {
|
|
kbTypeName = "sp_vote_keyboard"
|
|
}
|
|
loc := h.Localizer(user.Language)
|
|
_, _ = h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
|
ChatID: tu.ID(user.ChatID),
|
|
MessageID: msgID,
|
|
Text: loc.Template("chosen_keyboard", map[string]interface{}{"Name": loc.Message(kbTypeName)}),
|
|
})
|
|
_, err = h.App.TG().SendMessage(&telego.SendMessageParams{
|
|
ChatID: tu.ID(user.ChatID),
|
|
Text: loc.Message("ask_for_redmine"),
|
|
ParseMode: telego.ModeMarkdown,
|
|
ReplyMarkup: &telego.InlineKeyboardMarkup{InlineKeyboard: [][]telego.InlineKeyboardButton{
|
|
{
|
|
{
|
|
Text: loc.Message("yes"),
|
|
CallbackData: util.NewRedmineQuestionPayload(user.TelegramID, chat.TelegramID, true).String(),
|
|
},
|
|
{
|
|
Text: loc.Message("no"),
|
|
CallbackData: util.NewRedmineQuestionPayload(user.TelegramID, chat.TelegramID, false).String(),
|
|
},
|
|
},
|
|
}},
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) handleAnswerRedmine(answer util.Answer, chatID int64, msgID int, user *model.User) error {
|
|
loc := h.Localizer(user.Language)
|
|
if !answer.Result {
|
|
_, _ = h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
|
ChatID: tu.ID(user.ChatID),
|
|
MessageID: msgID,
|
|
Text: loc.Message("redmine_will_not_be_configured"),
|
|
})
|
|
return util.SendSetupDone(h.App.TG(), h.App.TGProfile().Username, user.ChatID, loc)
|
|
}
|
|
_, err := h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
|
ChatID: tu.ID(user.ChatID),
|
|
MessageID: msgID,
|
|
Text: loc.Message("please_send_redmine_url"),
|
|
})
|
|
store.RedmineSetups.Set(user.ChatID, &store.RedmineSetup{Chat: chatID})
|
|
return err
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) handleAnswerRedmineHours(answer util.Answer, chatID int64, msgID int, user *model.User) error {
|
|
loc := h.Localizer(user.Language)
|
|
message := "redmine_hours_will_not_be_configured"
|
|
if answer.Result {
|
|
message = "redmine_hours_will_be_configured"
|
|
}
|
|
|
|
_, _ = h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
|
ChatID: tu.ID(user.ChatID),
|
|
MessageID: msgID,
|
|
Text: loc.Message(message),
|
|
})
|
|
|
|
rs, found := store.RedmineSetups.Get(user.ChatID)
|
|
if found {
|
|
rs.SaveHours = answer.Result
|
|
if err := h.updateRedmineIntegration(rs, loc, user); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
_, err := h.App.TG().SendMessage(&telego.SendMessageParams{
|
|
ChatID: tu.ID(user.ChatID),
|
|
Text: loc.Message("should_send_poker_to_redmine"),
|
|
ParseMode: telego.ModeMarkdown,
|
|
ReplyMarkup: &telego.InlineKeyboardMarkup{InlineKeyboard: [][]telego.InlineKeyboardButton{
|
|
{
|
|
{
|
|
Text: loc.Message("yes"),
|
|
CallbackData: util.NewRedmineSendResultQuestionPayload(user.TelegramID, chatID, true).String(),
|
|
},
|
|
{
|
|
Text: loc.Message("no"),
|
|
CallbackData: util.NewRedmineSendResultQuestionPayload(user.TelegramID, chatID, false).String(),
|
|
},
|
|
},
|
|
}},
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) handleAnswerRedmineSendResults(answer util.Answer, msgID int, user *model.User) error {
|
|
loc := h.Localizer(user.Language)
|
|
if !answer.Result {
|
|
_, _ = h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
|
ChatID: tu.ID(user.ChatID),
|
|
MessageID: msgID,
|
|
Text: loc.Message("redmine_poker_will_not_be_configured"),
|
|
})
|
|
return util.SendSetupDone(h.App.TG(), h.App.TGProfile().Username, user.ChatID, loc)
|
|
}
|
|
_, err := h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
|
ChatID: tu.ID(user.ChatID),
|
|
MessageID: msgID,
|
|
Text: loc.Message("specify_result_field"),
|
|
})
|
|
rs, _ := store.RedmineSetups.Get(user.ChatID)
|
|
rs.WaitingForSPField = true
|
|
return err
|
|
}
|
|
|
|
func (h *CallbackQueryHandler) updateRedmineIntegration(rs *store.RedmineSetup, loc locale.Localizer, user *model.User) error {
|
|
dbChat, err := h.App.DB().ForChat().ByTelegramID(rs.Chat)
|
|
if dbChat == nil || dbChat.ID == 0 {
|
|
_ = util.SendInternalError(h.App.TG(), user.ChatID, loc)
|
|
return err
|
|
}
|
|
savedRedmine, err := h.App.DB().ForIntegration().LoadForChatAndType(dbChat.ID, model.RedmineIntegration)
|
|
if savedRedmine == nil || savedRedmine.ID == 0 {
|
|
_ = util.SendInternalError(h.App.TG(), user.ChatID, loc)
|
|
return err
|
|
}
|
|
var shouldUpdate bool
|
|
savedRS := savedRedmine.LoadRedmine()
|
|
if savedRS.SaveHours != rs.SaveHours {
|
|
savedRS.SaveHours = rs.SaveHours
|
|
shouldUpdate = true
|
|
}
|
|
if savedRS.SPFieldName != rs.SPFieldName {
|
|
savedRS.SPFieldName = rs.SPFieldName
|
|
shouldUpdate = true
|
|
}
|
|
if shouldUpdate {
|
|
savedRedmine.StoreRedmine(savedRS)
|
|
return h.App.DB().ForIntegration().Save(savedRedmine)
|
|
}
|
|
return nil
|
|
}
|