poker implementation
This commit is contained in:
parent
3a8a7c2a5c
commit
e0ba5347a1
@ -1,3 +1,5 @@
|
|||||||
POKER_BOT_POSTGRES_DSN=postgres://app:app@db:5432/app?sslmode=disable
|
POKER_BOT_POSTGRES_DSN=postgres://app:app@db:5432/app?sslmode=disable
|
||||||
POKER_BOT_TELEGRAM_TOKEN=token
|
POKER_BOT_TELEGRAM_TOKEN=token
|
||||||
|
POKER_BOT_WEBHOOK_URL=https://vegapokerbot.proxy.neur0tx.site/webhook
|
||||||
|
POKER_BOT_LISTEN=:3333
|
||||||
POKER_BOT_DEBUG=false
|
POKER_BOT_DEBUG=false
|
||||||
|
@ -23,7 +23,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
GOCACHE: /go
|
GOCACHE: /go
|
||||||
ports:
|
ports:
|
||||||
- ${MG_TELEGRAM_ADDRESS:-8090}:8090
|
- 3333:3333
|
||||||
command: make build run
|
command: make build run
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/logger"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/logger"
|
||||||
"github.com/mymmrac/telego"
|
"github.com/mymmrac/telego"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
@ -18,6 +20,7 @@ type App struct {
|
|||||||
Config *config.Config
|
Config *config.Config
|
||||||
Repositories *db.Repositories
|
Repositories *db.Repositories
|
||||||
Handlers map[handler.Type]handler.Handler
|
Handlers map[handler.Type]handler.Handler
|
||||||
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Start() error {
|
func (a *App) Start() error {
|
||||||
@ -41,7 +44,10 @@ func (a *App) Start() error {
|
|||||||
}
|
}
|
||||||
a.initHandlers()
|
a.initHandlers()
|
||||||
a.Logger.Info("Vega Poker Bot is running")
|
a.Logger.Info("Vega Poker Bot is running")
|
||||||
return a.longPoll()
|
if a.Config.Listen == "" || !a.isValidURL(a.Config.WebhookURL) {
|
||||||
|
return a.longPoll()
|
||||||
|
}
|
||||||
|
return a.listenWebhook()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Log() *zap.SugaredLogger {
|
func (a *App) Log() *zap.SugaredLogger {
|
||||||
@ -128,23 +134,60 @@ func (a *App) handler(update telego.Update) handler.Handler {
|
|||||||
return a.Handlers[handler.Noop]
|
return a.Handlers[handler.Noop]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) processUpdate(update telego.Update) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
a.Logger.Errorf("recovered from panic inside the handler: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := a.handler(update).Handle(update); err != nil {
|
||||||
|
a.Logger.Errorf("error while handling the update: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) longPoll() error {
|
func (a *App) longPoll() error {
|
||||||
|
_ = a.Telegram.DeleteWebhook(&telego.DeleteWebhookParams{})
|
||||||
updates, err := a.Telegram.UpdatesViaLongPolling(nil)
|
updates, err := a.Telegram.UpdatesViaLongPolling(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer a.Telegram.StopLongPolling()
|
defer a.Telegram.StopLongPolling()
|
||||||
for update := range updates {
|
for update := range updates {
|
||||||
go func() {
|
go a.processUpdate(update)
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
a.Logger.Errorf("recovered from panic inside the handler: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if err := a.handler(update).Handle(update); err != nil {
|
|
||||||
a.Logger.Errorf("error while handling the update: %s", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) listenWebhook() error {
|
||||||
|
uri, _ := url.Parse(a.Config.WebhookURL)
|
||||||
|
err := a.Telegram.SetWebhook(&telego.SetWebhookParams{
|
||||||
|
URL: a.Config.WebhookURL,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Logger.Debugf("fetching updates from webhook: %s", uri.Path)
|
||||||
|
updates, err := a.Telegram.UpdatesViaWebhook(uri.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
_ = a.Telegram.StartWebhook(a.Config.Listen)
|
||||||
|
}()
|
||||||
|
defer func() {
|
||||||
|
_ = a.Telegram.StopWebhook()
|
||||||
|
}()
|
||||||
|
for update := range updates {
|
||||||
|
go a.processUpdate(update)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) isValidURL(uri string) bool {
|
||||||
|
if uri == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := url.Parse(uri)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
@ -11,6 +11,8 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
PostgresDSN string `default:"" env:"POSTGRES_DSN" yaml:"postgres_dsn" toml:"postgres_dsn"`
|
PostgresDSN string `default:"" env:"POSTGRES_DSN" yaml:"postgres_dsn" toml:"postgres_dsn"`
|
||||||
TelegramToken string `default:"" env:"TELEGRAM_TOKEN" yaml:"telegram_token" toml:"telegram_token"`
|
TelegramToken string `default:"" env:"TELEGRAM_TOKEN" yaml:"telegram_token" toml:"telegram_token"`
|
||||||
|
WebhookURL string `default:"" env:"WEBHOOK_URL" yaml:"webhook_url" toml:"webhook_url"`
|
||||||
|
Listen string `default:"" env:"LISTEN" yaml:"listen" toml:"listen"`
|
||||||
Debug bool `default:"false" env:"DEBUG" yaml:"debug" toml:"debug"`
|
Debug bool `default:"false" env:"DEBUG" yaml:"debug" toml:"debug"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,35 +2,289 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"fmt"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/db/model"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/db/model"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/iface"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/iface"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/store"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/store"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/util"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/util"
|
||||||
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/locale"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/locale"
|
||||||
"github.com/mymmrac/telego"
|
"github.com/mymmrac/telego"
|
||||||
tu "github.com/mymmrac/telego/telegoutil"
|
tu "github.com/mymmrac/telego/telegoutil"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CallbackQueryHandler struct {
|
type CallbackQueryHandler struct {
|
||||||
app iface.App
|
iface.Base
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCallbackQueryHandler(app iface.App) Handler {
|
func NewCallbackQueryHandler(app iface.App) Handler {
|
||||||
return &CallbackQueryHandler{app: app}
|
return &CallbackQueryHandler{iface.NewBase(app, 0, 0)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *CallbackQueryHandler) Handle(wh telego.Update) error {
|
func (h *CallbackQueryHandler) Handle(wh telego.Update) error {
|
||||||
cq := wh.CallbackQuery
|
cq := wh.CallbackQuery
|
||||||
user, err := h.app.DB().ForUser().ByTelegramID(cq.From.ID)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if user != nil && user.ID > 0 {
|
user, err := h.App.DB().ForUser().ByID(chat.UserID)
|
||||||
return h.handleSetupKey(cq, user)
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
// @todo implement poker polls handling.
|
loc := h.Localizer(user.Language)
|
||||||
return errors.New("not implemented yet")
|
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 {
|
func (h *CallbackQueryHandler) handleSetupKey(cq *telego.CallbackQuery, user *model.User) error {
|
||||||
@ -58,7 +312,7 @@ func (h *CallbackQueryHandler) handleSetupKey(cq *telego.CallbackQuery, user *mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *CallbackQueryHandler) handleChooseKeyboard(pl util.Payload, msgID int, user *model.User) error {
|
func (h *CallbackQueryHandler) handleChooseKeyboard(pl util.Payload, msgID int, user *model.User) error {
|
||||||
cr := h.app.DB().ForChat()
|
cr := h.App.DB().ForChat()
|
||||||
result := pl.KeyboardChoice()
|
result := pl.KeyboardChoice()
|
||||||
if pl.User != user.TelegramID {
|
if pl.User != user.TelegramID {
|
||||||
return nil
|
return nil
|
||||||
@ -79,13 +333,13 @@ func (h *CallbackQueryHandler) handleChooseKeyboard(pl util.Payload, msgID int,
|
|||||||
if model.KeyboardType(result.Type) == model.StoryPointsKeyboard {
|
if model.KeyboardType(result.Type) == model.StoryPointsKeyboard {
|
||||||
kbTypeName = "sp_vote_keyboard"
|
kbTypeName = "sp_vote_keyboard"
|
||||||
}
|
}
|
||||||
loc := h.app.Localizer(user.Language)
|
loc := h.Localizer(user.Language)
|
||||||
_, _ = h.app.TG().EditMessageText(&telego.EditMessageTextParams{
|
_, _ = h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
||||||
ChatID: tu.ID(user.ChatID),
|
ChatID: tu.ID(user.ChatID),
|
||||||
MessageID: msgID,
|
MessageID: msgID,
|
||||||
Text: loc.Template("chosen_keyboard", map[string]interface{}{"Name": loc.Message(kbTypeName)}),
|
Text: loc.Template("chosen_keyboard", map[string]interface{}{"Name": loc.Message(kbTypeName)}),
|
||||||
})
|
})
|
||||||
_, err = h.app.TG().SendMessage(&telego.SendMessageParams{
|
_, err = h.App.TG().SendMessage(&telego.SendMessageParams{
|
||||||
ChatID: tu.ID(user.ChatID),
|
ChatID: tu.ID(user.ChatID),
|
||||||
Text: loc.Message("ask_for_redmine"),
|
Text: loc.Message("ask_for_redmine"),
|
||||||
ParseMode: telego.ModeMarkdown,
|
ParseMode: telego.ModeMarkdown,
|
||||||
@ -106,16 +360,16 @@ func (h *CallbackQueryHandler) handleChooseKeyboard(pl util.Payload, msgID int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *CallbackQueryHandler) handleAnswerRedmine(answer util.Answer, chatID int64, msgID int, user *model.User) error {
|
func (h *CallbackQueryHandler) handleAnswerRedmine(answer util.Answer, chatID int64, msgID int, user *model.User) error {
|
||||||
loc := h.app.Localizer(user.Language)
|
loc := h.Localizer(user.Language)
|
||||||
if !answer.Result {
|
if !answer.Result {
|
||||||
_, _ = h.app.TG().EditMessageText(&telego.EditMessageTextParams{
|
_, _ = h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
||||||
ChatID: tu.ID(user.ChatID),
|
ChatID: tu.ID(user.ChatID),
|
||||||
MessageID: msgID,
|
MessageID: msgID,
|
||||||
Text: loc.Message("redmine_will_not_be_configured"),
|
Text: loc.Message("redmine_will_not_be_configured"),
|
||||||
})
|
})
|
||||||
return util.SendSetupDone(h.app.TG(), user.ChatID, loc)
|
return util.SendSetupDone(h.App.TG(), user.ChatID, loc)
|
||||||
}
|
}
|
||||||
_, err := h.app.TG().EditMessageText(&telego.EditMessageTextParams{
|
_, err := h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
||||||
ChatID: tu.ID(user.ChatID),
|
ChatID: tu.ID(user.ChatID),
|
||||||
MessageID: msgID,
|
MessageID: msgID,
|
||||||
Text: loc.Message("please_send_redmine_url"),
|
Text: loc.Message("please_send_redmine_url"),
|
||||||
@ -125,13 +379,13 @@ func (h *CallbackQueryHandler) handleAnswerRedmine(answer util.Answer, chatID in
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *CallbackQueryHandler) handleAnswerRedmineHours(answer util.Answer, chatID int64, msgID int, user *model.User) error {
|
func (h *CallbackQueryHandler) handleAnswerRedmineHours(answer util.Answer, chatID int64, msgID int, user *model.User) error {
|
||||||
loc := h.app.Localizer(user.Language)
|
loc := h.Localizer(user.Language)
|
||||||
message := "redmine_hours_will_not_be_configured"
|
message := "redmine_hours_will_not_be_configured"
|
||||||
if answer.Result {
|
if answer.Result {
|
||||||
message = "redmine_hours_will_be_configured"
|
message = "redmine_hours_will_be_configured"
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = h.app.TG().EditMessageText(&telego.EditMessageTextParams{
|
_, _ = h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
||||||
ChatID: tu.ID(user.ChatID),
|
ChatID: tu.ID(user.ChatID),
|
||||||
MessageID: msgID,
|
MessageID: msgID,
|
||||||
Text: loc.Message(message),
|
Text: loc.Message(message),
|
||||||
@ -145,7 +399,7 @@ func (h *CallbackQueryHandler) handleAnswerRedmineHours(answer util.Answer, chat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := h.app.TG().SendMessage(&telego.SendMessageParams{
|
_, err := h.App.TG().SendMessage(&telego.SendMessageParams{
|
||||||
ChatID: tu.ID(user.ChatID),
|
ChatID: tu.ID(user.ChatID),
|
||||||
Text: loc.Message("should_send_poker_to_redmine"),
|
Text: loc.Message("should_send_poker_to_redmine"),
|
||||||
ParseMode: telego.ModeMarkdown,
|
ParseMode: telego.ModeMarkdown,
|
||||||
@ -167,16 +421,16 @@ func (h *CallbackQueryHandler) handleAnswerRedmineHours(answer util.Answer, chat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *CallbackQueryHandler) handleAnswerRedmineSendResults(answer util.Answer, msgID int, user *model.User) error {
|
func (h *CallbackQueryHandler) handleAnswerRedmineSendResults(answer util.Answer, msgID int, user *model.User) error {
|
||||||
loc := h.app.Localizer(user.Language)
|
loc := h.Localizer(user.Language)
|
||||||
if !answer.Result {
|
if !answer.Result {
|
||||||
_, _ = h.app.TG().EditMessageText(&telego.EditMessageTextParams{
|
_, _ = h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
||||||
ChatID: tu.ID(user.ChatID),
|
ChatID: tu.ID(user.ChatID),
|
||||||
MessageID: msgID,
|
MessageID: msgID,
|
||||||
Text: loc.Message("redmine_poker_will_not_be_configured"),
|
Text: loc.Message("redmine_poker_will_not_be_configured"),
|
||||||
})
|
})
|
||||||
return util.SendSetupDone(h.app.TG(), user.ChatID, loc)
|
return util.SendSetupDone(h.App.TG(), user.ChatID, loc)
|
||||||
}
|
}
|
||||||
_, err := h.app.TG().EditMessageText(&telego.EditMessageTextParams{
|
_, err := h.App.TG().EditMessageText(&telego.EditMessageTextParams{
|
||||||
ChatID: tu.ID(user.ChatID),
|
ChatID: tu.ID(user.ChatID),
|
||||||
MessageID: msgID,
|
MessageID: msgID,
|
||||||
Text: loc.Message("specify_result_field"),
|
Text: loc.Message("specify_result_field"),
|
||||||
@ -187,14 +441,14 @@ func (h *CallbackQueryHandler) handleAnswerRedmineSendResults(answer util.Answer
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *CallbackQueryHandler) updateRedmineIntegration(rs *store.RedmineSetup, loc locale.Localizer, user *model.User) error {
|
func (h *CallbackQueryHandler) updateRedmineIntegration(rs *store.RedmineSetup, loc locale.Localizer, user *model.User) error {
|
||||||
dbChat, err := h.app.DB().ForChat().ByTelegramID(rs.Chat)
|
dbChat, err := h.App.DB().ForChat().ByTelegramID(rs.Chat)
|
||||||
if dbChat == nil || dbChat.ID == 0 {
|
if dbChat == nil || dbChat.ID == 0 {
|
||||||
_ = util.SendInternalError(h.app.TG(), user.ChatID, loc)
|
_ = util.SendInternalError(h.App.TG(), user.ChatID, loc)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
savedRedmine, err := h.app.DB().ForIntegration().LoadForChatAndType(dbChat.ID, model.RedmineIntegration)
|
savedRedmine, err := h.App.DB().ForIntegration().LoadForChatAndType(dbChat.ID, model.RedmineIntegration)
|
||||||
if savedRedmine == nil || savedRedmine.ID == 0 {
|
if savedRedmine == nil || savedRedmine.ID == 0 {
|
||||||
_ = util.SendInternalError(h.app.TG(), user.ChatID, loc)
|
_ = util.SendInternalError(h.App.TG(), user.ChatID, loc)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var shouldUpdate bool
|
var shouldUpdate bool
|
||||||
@ -209,7 +463,7 @@ func (h *CallbackQueryHandler) updateRedmineIntegration(rs *store.RedmineSetup,
|
|||||||
}
|
}
|
||||||
if shouldUpdate {
|
if shouldUpdate {
|
||||||
savedRedmine.StoreRedmine(savedRS)
|
savedRedmine.StoreRedmine(savedRS)
|
||||||
return h.app.DB().ForIntegration().Save(savedRedmine)
|
return h.App.DB().ForIntegration().Save(savedRedmine)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
package chat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/iface"
|
|
||||||
"github.com/mymmrac/telego"
|
|
||||||
tu "github.com/mymmrac/telego/telegoutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Poll struct {
|
|
||||||
iface.Base
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPoll(app iface.App, userID, chatID int64) *Poll {
|
|
||||||
return &Poll{iface.NewBase(app, userID, chatID)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Poll) Handle(wh telego.Update) error {
|
|
||||||
if wh.Message.Chat.Type == telego.ChatTypePrivate {
|
|
||||||
_, err := h.App.TG().SendMessage(&telego.SendMessageParams{
|
|
||||||
ChatID: tu.ID(wh.Message.Chat.ID),
|
|
||||||
Text: h.Localizer(wh.Message.From.LanguageCode).Message("use_this_command_in_group"),
|
|
||||||
ParseMode: telego.ModeMarkdown,
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// @todo implement poker polls.
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -13,21 +13,21 @@ import (
|
|||||||
const MaxChatMembers = 32
|
const MaxChatMembers = 32
|
||||||
|
|
||||||
type ChatMemberUpdatedHandler struct {
|
type ChatMemberUpdatedHandler struct {
|
||||||
app iface.App
|
iface.Base
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChatMemberUpdatedHandler(app iface.App) *ChatMemberUpdatedHandler {
|
func NewChatMemberUpdatedHandler(app iface.App) *ChatMemberUpdatedHandler {
|
||||||
return &ChatMemberUpdatedHandler{app: app}
|
return &ChatMemberUpdatedHandler{iface.NewBase(app, 0, 0)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ChatMemberUpdatedHandler) Handle(wh telego.Update) error {
|
func (h *ChatMemberUpdatedHandler) Handle(wh telego.Update) error {
|
||||||
cm := wh.MyChatMember
|
cm := wh.MyChatMember
|
||||||
if !cm.OldChatMember.MemberIsMember() && cm.OldChatMember.MemberUser().ID == h.app.TGProfile().ID &&
|
if !cm.OldChatMember.MemberIsMember() && cm.OldChatMember.MemberUser().ID == h.App.TGProfile().ID &&
|
||||||
cm.NewChatMember.MemberIsMember() && cm.NewChatMember.MemberUser().ID == h.app.TGProfile().ID {
|
cm.NewChatMember.MemberIsMember() && cm.NewChatMember.MemberUser().ID == h.App.TGProfile().ID {
|
||||||
return h.handleAddToChat(wh.MyChatMember.Chat)
|
return h.handleAddToChat(wh.MyChatMember.Chat)
|
||||||
}
|
}
|
||||||
if cm.OldChatMember.MemberIsMember() && cm.OldChatMember.MemberUser().ID == h.app.TGProfile().ID &&
|
if cm.OldChatMember.MemberIsMember() && cm.OldChatMember.MemberUser().ID == h.App.TGProfile().ID &&
|
||||||
!cm.NewChatMember.MemberIsMember() && cm.NewChatMember.MemberUser().ID == h.app.TGProfile().ID {
|
!cm.NewChatMember.MemberIsMember() && cm.NewChatMember.MemberUser().ID == h.App.TGProfile().ID {
|
||||||
return h.handleRemoveFromChat(wh.MyChatMember.Chat)
|
return h.handleRemoveFromChat(wh.MyChatMember.Chat)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,42 +36,42 @@ func (h *ChatMemberUpdatedHandler) Handle(wh telego.Update) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ChatMemberUpdatedHandler) handleAddToChat(tgChat telego.Chat) error {
|
func (h *ChatMemberUpdatedHandler) handleAddToChat(tgChat telego.Chat) error {
|
||||||
cr := h.app.DB().ForChat()
|
cr := h.App.DB().ForChat()
|
||||||
chat, err := cr.ByTelegramID(tgChat.ID)
|
chat, err := cr.ByTelegramID(tgChat.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = util.SendInternalError(h.app.TG(), tgChat.ID, nil)
|
_ = util.SendInternalError(h.App.TG(), tgChat.ID, nil)
|
||||||
h.leaveChat(tgChat.ID)
|
h.leaveChat(tgChat.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := h.getRegisteredAdmin(tgChat.ID)
|
user, err := h.getRegisteredAdmin(tgChat.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = util.SendInternalError(h.app.TG(), tgChat.ID, nil)
|
_ = util.SendInternalError(h.App.TG(), tgChat.ID, nil)
|
||||||
h.leaveChat(tgChat.ID)
|
h.leaveChat(tgChat.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if user == nil || user.ID == 0 {
|
if user == nil || user.ID == 0 {
|
||||||
_, err = h.app.TG().SendMessage(&telego.SendMessageParams{
|
_, err = h.App.TG().SendMessage(&telego.SendMessageParams{
|
||||||
ChatID: tu.ID(tgChat.ID),
|
ChatID: tu.ID(tgChat.ID),
|
||||||
Text: h.app.Localizer(language.English.String()).Message("you_should_register_first"),
|
Text: h.Localizer(language.English.String()).Message("you_should_register_first"),
|
||||||
ParseMode: telego.ModeMarkdown,
|
ParseMode: telego.ModeMarkdown,
|
||||||
})
|
})
|
||||||
h.leaveChat(tgChat.ID)
|
h.leaveChat(tgChat.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
loc := h.app.Localizer(user.Language)
|
loc := h.Localizer(user.Language)
|
||||||
|
|
||||||
totalMembers, err := h.app.TG().GetChatMemberCount(&telego.GetChatMemberCountParams{
|
totalMembers, err := h.App.TG().GetChatMemberCount(&telego.GetChatMemberCountParams{
|
||||||
ChatID: tu.ID(tgChat.ID),
|
ChatID: tu.ID(tgChat.ID),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = util.SendInternalError(h.app.TG(), tgChat.ID, nil)
|
_ = util.SendInternalError(h.App.TG(), tgChat.ID, nil)
|
||||||
h.leaveChat(tgChat.ID)
|
h.leaveChat(tgChat.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if *totalMembers > MaxChatMembers {
|
if *totalMembers > MaxChatMembers {
|
||||||
_, err = h.app.TG().SendMessage(&telego.SendMessageParams{
|
_, err = h.App.TG().SendMessage(&telego.SendMessageParams{
|
||||||
ChatID: tu.ID(tgChat.ID),
|
ChatID: tu.ID(tgChat.ID),
|
||||||
Text: loc.Template("too_many_members_in_the_group", map[string]interface{}{"Limit": MaxChatMembers}),
|
Text: loc.Template("too_many_members_in_the_group", map[string]interface{}{"Limit": MaxChatMembers}),
|
||||||
ParseMode: telego.ModeMarkdown,
|
ParseMode: telego.ModeMarkdown,
|
||||||
@ -87,27 +87,27 @@ func (h *ChatMemberUpdatedHandler) handleAddToChat(tgChat telego.Chat) error {
|
|||||||
}
|
}
|
||||||
err := cr.Save(chat)
|
err := cr.Save(chat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = util.SendInternalError(h.app.TG(), tgChat.ID, loc)
|
_ = util.SendInternalError(h.App.TG(), tgChat.ID, loc)
|
||||||
h.leaveChat(tgChat.ID)
|
h.leaveChat(tgChat.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
chat.UserID = user.ID
|
chat.UserID = user.ID
|
||||||
err := h.app.DB().ForIntegration().DeleteForChat(chat.ID)
|
err := h.App.DB().ForIntegration().DeleteForChat(chat.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = util.SendInternalError(h.app.TG(), tgChat.ID, loc)
|
_ = util.SendInternalError(h.App.TG(), tgChat.ID, loc)
|
||||||
h.leaveChat(tgChat.ID)
|
h.leaveChat(tgChat.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = cr.Save(chat)
|
err = cr.Save(chat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = util.SendInternalError(h.app.TG(), tgChat.ID, loc)
|
_ = util.SendInternalError(h.App.TG(), tgChat.ID, loc)
|
||||||
h.leaveChat(tgChat.ID)
|
h.leaveChat(tgChat.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = h.app.TG().SendMessage(&telego.SendMessageParams{
|
_, _ = h.App.TG().SendMessage(&telego.SendMessageParams{
|
||||||
ChatID: tu.ID(user.ChatID),
|
ChatID: tu.ID(user.ChatID),
|
||||||
Text: loc.Template("bot_was_added", map[string]interface{}{"Name": tgChat.Title}),
|
Text: loc.Template("bot_was_added", map[string]interface{}{"Name": tgChat.Title}),
|
||||||
ParseMode: telego.ModeMarkdown,
|
ParseMode: telego.ModeMarkdown,
|
||||||
@ -117,17 +117,17 @@ func (h *ChatMemberUpdatedHandler) handleAddToChat(tgChat telego.Chat) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ChatMemberUpdatedHandler) handleRemoveFromChat(tgChat telego.Chat) error {
|
func (h *ChatMemberUpdatedHandler) handleRemoveFromChat(tgChat telego.Chat) error {
|
||||||
cr := h.app.DB().ForChat()
|
cr := h.App.DB().ForChat()
|
||||||
chat, err := cr.ByTelegramID(tgChat.ID)
|
chat, err := cr.ByTelegramID(tgChat.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if chat != nil && chat.ID > 0 {
|
if chat != nil && chat.ID > 0 {
|
||||||
user, _ := h.app.DB().ForUser().ByID(chat.UserID)
|
user, _ := h.App.DB().ForUser().ByID(chat.UserID)
|
||||||
if user != nil && user.ID > 0 && user.ChatID > 0 {
|
if user != nil && user.ID > 0 && user.ChatID > 0 {
|
||||||
_, _ = h.app.TG().SendMessage(&telego.SendMessageParams{
|
_, _ = h.App.TG().SendMessage(&telego.SendMessageParams{
|
||||||
ChatID: tu.ID(user.ChatID),
|
ChatID: tu.ID(user.ChatID),
|
||||||
Text: h.app.Localizer(user.Language).Template(
|
Text: h.Localizer(user.Language).Template(
|
||||||
"bot_was_removed_from_group", map[string]interface{}{"Name": tgChat.Title}),
|
"bot_was_removed_from_group", map[string]interface{}{"Name": tgChat.Title}),
|
||||||
ParseMode: telego.ModeMarkdown,
|
ParseMode: telego.ModeMarkdown,
|
||||||
})
|
})
|
||||||
@ -138,7 +138,7 @@ func (h *ChatMemberUpdatedHandler) handleRemoveFromChat(tgChat telego.Chat) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ChatMemberUpdatedHandler) sendKeyboardChooser(chat telego.Chat, user *model.User, loc locale.Localizer) error {
|
func (h *ChatMemberUpdatedHandler) sendKeyboardChooser(chat telego.Chat, user *model.User, loc locale.Localizer) error {
|
||||||
_, err := h.app.TG().SendMessage(&telego.SendMessageParams{
|
_, err := h.App.TG().SendMessage(&telego.SendMessageParams{
|
||||||
ChatID: tu.ID(user.ChatID),
|
ChatID: tu.ID(user.ChatID),
|
||||||
Text: loc.Template("choose_keyboard", map[string]interface{}{"Name": chat.Title}),
|
Text: loc.Template("choose_keyboard", map[string]interface{}{"Name": chat.Title}),
|
||||||
ParseMode: telego.ModeMarkdown,
|
ParseMode: telego.ModeMarkdown,
|
||||||
@ -165,7 +165,7 @@ func (h *ChatMemberUpdatedHandler) sendKeyboardChooser(chat telego.Chat, user *m
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ChatMemberUpdatedHandler) getRegisteredAdmin(chatID int64) (*model.User, error) {
|
func (h *ChatMemberUpdatedHandler) getRegisteredAdmin(chatID int64) (*model.User, error) {
|
||||||
admins, err := h.app.TG().GetChatAdministrators(&telego.GetChatAdministratorsParams{
|
admins, err := h.App.TG().GetChatAdministrators(&telego.GetChatAdministratorsParams{
|
||||||
ChatID: tu.ID(chatID),
|
ChatID: tu.ID(chatID),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -175,7 +175,7 @@ func (h *ChatMemberUpdatedHandler) getRegisteredAdmin(chatID int64) (*model.User
|
|||||||
for i, admin := range admins {
|
for i, admin := range admins {
|
||||||
adminIDs[i] = admin.MemberUser().ID
|
adminIDs[i] = admin.MemberUser().ID
|
||||||
}
|
}
|
||||||
dbAdmins, err := h.app.DB().ForUser().ByTelegramIDs(adminIDs)
|
dbAdmins, err := h.App.DB().ForUser().ByTelegramIDs(adminIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -186,7 +186,7 @@ func (h *ChatMemberUpdatedHandler) getRegisteredAdmin(chatID int64) (*model.User
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ChatMemberUpdatedHandler) leaveChat(chatID int64) {
|
func (h *ChatMemberUpdatedHandler) leaveChat(chatID int64) {
|
||||||
_ = h.app.TG().LeaveChat(&telego.LeaveChatParams{
|
_ = h.App.TG().LeaveChat(&telego.LeaveChatParams{
|
||||||
ChatID: tu.ID(chatID),
|
ChatID: tu.ID(chatID),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
109
internal/handler/group/poll.go
Normal file
109
internal/handler/group/poll.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"
|
||||||
|
"github.com/mymmrac/telego"
|
||||||
|
tu "github.com/mymmrac/telego/telegoutil"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Poll struct {
|
||||||
|
iface.Base
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPoll(app iface.App, userID, chatID int64) *Poll {
|
||||||
|
return &Poll{iface.NewBase(app, userID, chatID)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Poll) Handle(wh telego.Update) error {
|
||||||
|
if wh.Message.Chat.Type == telego.ChatTypePrivate {
|
||||||
|
_, err := h.App.TG().SendMessage(&telego.SendMessageParams{
|
||||||
|
ChatID: tu.ID(wh.Message.Chat.ID),
|
||||||
|
Text: h.Localizer(wh.Message.From.LanguageCode).Message("use_this_command_in_group"),
|
||||||
|
ParseMode: telego.ModeMarkdown,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
chat, err := h.App.DB().ForChat().ByTelegramIDWithIntegrations(wh.Message.Chat.ID)
|
||||||
|
if err != nil {
|
||||||
|
_ = util.SendInternalError(h.App.TG(), wh.Message.Chat.ID, h.Localizer(language.English.String()))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user, err := h.App.DB().ForUser().ByID(chat.UserID)
|
||||||
|
if err != nil {
|
||||||
|
_ = util.SendInternalError(h.App.TG(), wh.Message.Chat.ID, h.Localizer(language.English.String()))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
loc := h.Localizer(user.Language)
|
||||||
|
_ = loc
|
||||||
|
if len(wh.Message.Entities) != 1 ||
|
||||||
|
(len(wh.Message.Entities) == 1 && wh.Message.Entities[0].Type != telego.EntityTypeBotCommand) ||
|
||||||
|
(len(wh.Message.Entities) == 1 && wh.Message.Entities[0].Offset != 0) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
taskID int
|
||||||
|
canRedmine bool
|
||||||
|
)
|
||||||
|
taskInfo := strings.TrimSpace(wh.Message.Text[wh.Message.Entities[0].Length:])
|
||||||
|
if taskInfo != "" {
|
||||||
|
for _, integrationData := range chat.Integrations {
|
||||||
|
id, info := integration.New(integrationData, h.App.Log()).GetTaskInfo(taskInfo)
|
||||||
|
if id > 0 {
|
||||||
|
taskID = id
|
||||||
|
taskInfo = info
|
||||||
|
canRedmine = integrationData.Type == model.RedmineIntegration
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dotOrColon := "."
|
||||||
|
if taskInfo != "" {
|
||||||
|
dotOrColon = ": "
|
||||||
|
}
|
||||||
|
|
||||||
|
var kb telego.ReplyMarkup
|
||||||
|
switch chat.KeyboardType {
|
||||||
|
case model.StandardKeyboard:
|
||||||
|
kb = util.StandardVoteKeyboard(wh.Message.Chat.ID, loc)
|
||||||
|
case model.StoryPointsKeyboard:
|
||||||
|
kb = util.StoryPointsVoteKeyboard(wh.Message.Chat.ID, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := h.App.TG().SendMessage(&telego.SendMessageParams{
|
||||||
|
ChatID: tu.ID(wh.Message.Chat.ID),
|
||||||
|
Text: fmt.Sprintf("%s\n%s",
|
||||||
|
loc.Template("poker_start", map[string]interface{}{
|
||||||
|
"Name": wh.Message.From.Username,
|
||||||
|
"DotOrColon": dotOrColon,
|
||||||
|
"Subject": taskInfo,
|
||||||
|
}),
|
||||||
|
loc.Message("no_votes_yet"),
|
||||||
|
),
|
||||||
|
ReplyMarkup: kb,
|
||||||
|
ParseMode: telego.ModeMarkdown,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
store.Polls.Set(msg.MessageID, store.PollState{
|
||||||
|
Initiator: wh.Message.From.Username,
|
||||||
|
Subject: taskInfo,
|
||||||
|
TaskID: taskID,
|
||||||
|
Votes: []store.MemberVote{},
|
||||||
|
Result: store.PollResult{},
|
||||||
|
CanRedmine: canRedmine,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,131 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import "github.com/mymmrac/telego"
|
|
||||||
|
|
||||||
func StandardVoteKeyboard() *telego.InlineKeyboardMarkup {
|
|
||||||
return &telego.InlineKeyboardMarkup{
|
|
||||||
InlineKeyboard: [][]telego.InlineKeyboardButton{
|
|
||||||
{
|
|
||||||
{
|
|
||||||
Text: "0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "0.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
{
|
|
||||||
Text: "3",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "8",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "13",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
{
|
|
||||||
Text: "20",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "100",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func StoryPointsVoteKeyboard() *telego.InlineKeyboardMarkup {
|
|
||||||
return &telego.InlineKeyboardMarkup{
|
|
||||||
InlineKeyboard: [][]telego.InlineKeyboardButton{
|
|
||||||
{
|
|
||||||
{
|
|
||||||
Text: "0.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "1.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{
|
|
||||||
Text: "2.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "3",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "3.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "4",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{
|
|
||||||
Text: "4.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "5.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "6",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{
|
|
||||||
Text: "6.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "7",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "7.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "8",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{
|
|
||||||
Text: "8.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "9",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "9.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "10",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,7 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/group"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/chat"
|
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/iface"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/iface"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/store"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/store"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/util"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/util"
|
||||||
@ -11,39 +10,34 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MessageHandler struct {
|
type MessageHandler struct {
|
||||||
app iface.App
|
iface.Base
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMessageHandler(app iface.App) Handler {
|
func NewMessageHandler(app iface.App) Handler {
|
||||||
return &MessageHandler{app: app}
|
return &MessageHandler{iface.NewBase(app, 0, 0)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) Handle(wh telego.Update) error {
|
func (h *MessageHandler) Handle(wh telego.Update) error {
|
||||||
if wh.Message.From != nil &&
|
if wh.Message.From == nil {
|
||||||
wh.Message.Chat.Type == telego.ChatTypePrivate {
|
return nil
|
||||||
if util.MatchCommand("poll", wh.Message) {
|
}
|
||||||
return chat.NewPoll(h.app, wh.Message.From.ID, wh.Message.Chat.ID).Handle(wh)
|
if wh.Message.Chat.Type == telego.ChatTypePrivate {
|
||||||
}
|
|
||||||
if util.MatchCommand("start", wh.Message) {
|
if util.MatchCommand("start", wh.Message) {
|
||||||
return wizard.NewRegister(h.app, wh.Message.From.ID, wh.Message.Chat.ID).Handle(wh)
|
return wizard.NewRegister(h.App, wh.Message.From.ID, wh.Message.Chat.ID).Handle(wh)
|
||||||
}
|
}
|
||||||
if util.MatchCommand("help", wh.Message) {
|
if util.MatchCommand("help", wh.Message) {
|
||||||
return wizard.NewHelpCommand(h.app, wh.Message.From.ID, wh.Message.Chat.ID).Handle(wh)
|
return wizard.NewHelpCommand(h.App, wh.Message.From.ID, wh.Message.Chat.ID).Handle(wh)
|
||||||
}
|
}
|
||||||
|
|
||||||
setup, found := store.RedmineSetups.Get(wh.Message.Chat.ID)
|
setup, found := store.RedmineSetups.Get(wh.Message.Chat.ID)
|
||||||
if found {
|
if found {
|
||||||
return wizard.NewRedmineSetup(h.app, wh.Message.From.ID, wh.Message.Chat.ID, setup).Handle(wh)
|
return wizard.NewRedmineSetup(h.App, wh.Message.From.ID, wh.Message.Chat.ID, setup).Handle(wh)
|
||||||
}
|
}
|
||||||
|
|
||||||
return wizard.NewUnknownCommand(h.app, wh.Message.From.ID, wh.Message.Chat.ID).Handle(wh)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove debug statement below.
|
if util.MatchCommand("poll", wh.Message) {
|
||||||
h.app.Log().Debugf("New Message: %s", func(msg *telego.Message) string {
|
return group.NewPoll(h.App, wh.Message.From.ID, wh.Message.Chat.ID).Handle(wh)
|
||||||
data, _ := json.Marshal(msg)
|
}
|
||||||
return string(data)
|
|
||||||
}(wh.Message))
|
|
||||||
|
|
||||||
return nil
|
return wizard.NewUnknownCommand(h.App, wh.Message.From.ID, wh.Message.Chat.ID).Handle(wh)
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cache, err := otter.MustBuilder[int64, PollState](10_000).
|
cache, err := otter.MustBuilder[int, PollState](10_000).
|
||||||
Cost(func(key int64, value PollState) uint32 {
|
Cost(func(key int, value PollState) uint32 {
|
||||||
return 1
|
return 1
|
||||||
}).
|
}).
|
||||||
WithTTL(time.Hour * 24 * 7).
|
WithTTL(time.Hour * 24 * 7).
|
||||||
|
@ -3,24 +3,43 @@ package store
|
|||||||
import (
|
import (
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/util"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/handler/util"
|
||||||
"github.com/maypok86/otter"
|
"github.com/maypok86/otter"
|
||||||
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MemberVote struct {
|
type MemberVote struct {
|
||||||
util.Member
|
util.Member
|
||||||
Vote float32
|
Vote float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type PollResult struct {
|
type PollResult struct {
|
||||||
Max float32
|
Max float64
|
||||||
Min float32
|
Min float64
|
||||||
Avg float32
|
Avg float64
|
||||||
Halved float32
|
RoundHalf float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type PollState struct {
|
type PollState struct {
|
||||||
Members []util.Member
|
Initiator string
|
||||||
Votes []MemberVote
|
Subject string
|
||||||
Result PollResult
|
TaskID int
|
||||||
|
Votes []MemberVote
|
||||||
|
Result PollResult
|
||||||
|
CanRedmine bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var Polls otter.Cache[int64, PollState]
|
func (p *PollState) Calculate() {
|
||||||
|
var sum float64
|
||||||
|
for _, vote := range p.Votes {
|
||||||
|
sum += vote.Vote
|
||||||
|
if vote.Vote > p.Result.Max {
|
||||||
|
p.Result.Max = vote.Vote
|
||||||
|
}
|
||||||
|
if vote.Vote < p.Result.Min {
|
||||||
|
p.Result.Min = vote.Vote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Result.Avg = sum / float64(len(p.Votes))
|
||||||
|
p.Result.RoundHalf = math.Round(p.Result.Avg/0.5) * 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
var Polls otter.Cache[int, PollState]
|
||||||
|
191
internal/handler/util/keyboard.go
Normal file
191
internal/handler/util/keyboard.go
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/locale"
|
||||||
|
"github.com/mymmrac/telego"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StandardVoteKeyboard(chatID int64, loc locale.Localizer) *telego.InlineKeyboardMarkup {
|
||||||
|
return &telego.InlineKeyboardMarkup{
|
||||||
|
InlineKeyboard: [][]telego.InlineKeyboardButton{
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Text: "0",
|
||||||
|
CallbackData: NewVotePayload(chatID, 0).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "0.5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 0.5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "1",
|
||||||
|
CallbackData: NewVotePayload(chatID, 1).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "2",
|
||||||
|
CallbackData: NewVotePayload(chatID, 2).String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
{
|
||||||
|
Text: "3",
|
||||||
|
CallbackData: NewVotePayload(chatID, 3).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "8",
|
||||||
|
CallbackData: NewVotePayload(chatID, 8).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "13",
|
||||||
|
CallbackData: NewVotePayload(chatID, 13).String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
{
|
||||||
|
Text: "20",
|
||||||
|
CallbackData: NewVotePayload(chatID, 20).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "40",
|
||||||
|
CallbackData: NewVotePayload(chatID, 40).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "100",
|
||||||
|
CallbackData: NewVotePayload(chatID, 100).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "?",
|
||||||
|
CallbackData: NewVotePayload(chatID, 0).String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Text: loc.Message("finish_vote"),
|
||||||
|
CallbackData: NewVoteFinishPayload(chatID).String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StoryPointsVoteKeyboard(chatID int64, loc locale.Localizer) *telego.InlineKeyboardMarkup {
|
||||||
|
return &telego.InlineKeyboardMarkup{
|
||||||
|
InlineKeyboard: [][]telego.InlineKeyboardButton{
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Text: "0.5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 0.5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "1",
|
||||||
|
CallbackData: NewVotePayload(chatID, 1).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "1.5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 1.5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "2",
|
||||||
|
CallbackData: NewVotePayload(chatID, 2).String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Text: "2.5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 2.5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "3",
|
||||||
|
CallbackData: NewVotePayload(chatID, 3).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "3.5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 3.5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "4",
|
||||||
|
CallbackData: NewVotePayload(chatID, 4).String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Text: "4.5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 4.5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "5.5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 5.5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "6",
|
||||||
|
CallbackData: NewVotePayload(chatID, 6).String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Text: "6.5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 6.5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "7",
|
||||||
|
CallbackData: NewVotePayload(chatID, 7).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "7.5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 7.5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "8",
|
||||||
|
CallbackData: NewVotePayload(chatID, 8).String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Text: "8.5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 8.5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "9",
|
||||||
|
CallbackData: NewVotePayload(chatID, 9).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "9.5",
|
||||||
|
CallbackData: NewVotePayload(chatID, 9.5).String(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "10",
|
||||||
|
CallbackData: NewVotePayload(chatID, 10).String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Text: loc.Message("finish_vote"),
|
||||||
|
CallbackData: NewVoteFinishPayload(chatID).String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendRedmineKeyboard(chatID int64, result float64, loc locale.Localizer) *telego.InlineKeyboardMarkup {
|
||||||
|
return &telego.InlineKeyboardMarkup{
|
||||||
|
InlineKeyboard: [][]telego.InlineKeyboardButton{
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Text: loc.Message("send_result_redmine"),
|
||||||
|
CallbackData: NewRedmineSendResultPayload(chatID, result).String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,8 @@ const (
|
|||||||
PayloadActionYesNo = iota
|
PayloadActionYesNo = iota
|
||||||
PayloadActionVote
|
PayloadActionVote
|
||||||
PayloadActionChooseKeyboard
|
PayloadActionChooseKeyboard
|
||||||
|
PayloadActionVoteFinish
|
||||||
|
PayloadActionVoteSendRedmine
|
||||||
)
|
)
|
||||||
|
|
||||||
type QuestionType uint8
|
type QuestionType uint8
|
||||||
@ -21,10 +23,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Payload struct {
|
type Payload struct {
|
||||||
User int64 `json:"u,omitempty"`
|
User int64 `json:"u,omitempty"`
|
||||||
Chat int64 `json:"c,omitempty"`
|
Chat int64 `json:"c,omitempty"`
|
||||||
Action PayloadAction `json:"a"`
|
Message int `json:"m,omitempty"`
|
||||||
Data json.RawMessage `json:"d,omitempty"`
|
Action PayloadAction `json:"a"`
|
||||||
|
Data json.RawMessage `json:"d,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Payload) String() string {
|
func (p Payload) String() string {
|
||||||
@ -66,7 +69,7 @@ type KBChooserData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Vote struct {
|
type Vote struct {
|
||||||
Vote float32 `json:"v"`
|
Vote float64 `json:"v"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Answer struct {
|
type Answer struct {
|
||||||
@ -85,7 +88,7 @@ func NewKeyboardChooserPayload(userID, chatID int64, kbType uint8) *Payload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVotePayload(chatID int64, vote float32) *Payload {
|
func NewVotePayload(chatID int64, vote float64) *Payload {
|
||||||
return &Payload{
|
return &Payload{
|
||||||
Action: PayloadActionVote,
|
Action: PayloadActionVote,
|
||||||
Chat: chatID,
|
Chat: chatID,
|
||||||
@ -93,6 +96,13 @@ func NewVotePayload(chatID int64, vote float32) *Payload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewVoteFinishPayload(chatID int64) *Payload {
|
||||||
|
return &Payload{
|
||||||
|
Action: PayloadActionVoteFinish,
|
||||||
|
Chat: chatID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewRedmineQuestionPayload(userID int64, chatID int64, isYes bool) *Payload {
|
func NewRedmineQuestionPayload(userID int64, chatID int64, isYes bool) *Payload {
|
||||||
return &Payload{
|
return &Payload{
|
||||||
Action: PayloadActionYesNo,
|
Action: PayloadActionYesNo,
|
||||||
@ -120,6 +130,14 @@ func NewRedmineSendResultQuestionPayload(userID int64, chatID int64, isYes bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewRedmineSendResultPayload(chatID int64, resultToSend float64) *Payload {
|
||||||
|
return &Payload{
|
||||||
|
Action: PayloadActionVoteSendRedmine,
|
||||||
|
Chat: chatID,
|
||||||
|
Data: marshal(Vote{Vote: resultToSend}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func marshal(val interface{}) json.RawMessage {
|
func marshal(val interface{}) json.RawMessage {
|
||||||
data, _ := json.Marshal(val)
|
data, _ := json.Marshal(val)
|
||||||
return data
|
return data
|
||||||
|
@ -5,12 +5,13 @@ import (
|
|||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/iface"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/iface"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/null"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/null"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/redmine"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/redmine"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(dbModel model.Integration) iface.Integration {
|
func New(dbModel model.Integration, log *zap.SugaredLogger) iface.Integration {
|
||||||
switch dbModel.Type {
|
switch dbModel.Type {
|
||||||
case model.RedmineIntegration:
|
case model.RedmineIntegration:
|
||||||
return redmine.New(dbModel.Params)
|
return redmine.New(dbModel.Params, log)
|
||||||
default:
|
default:
|
||||||
return null.New()
|
return null.New()
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package iface
|
package iface
|
||||||
|
|
||||||
type Integration interface {
|
type Integration interface {
|
||||||
GetTaskInfoText(input string) string
|
GetTaskInfo(input string) (int, string)
|
||||||
|
PushVoteResult(id int, result float64) error
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,10 @@ func New() iface.Integration {
|
|||||||
return &Null{}
|
return &Null{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Null) GetTaskInfoText() string {
|
func (n *Null) GetTaskInfo(string) (int, string) {
|
||||||
return ""
|
return 0, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Null) PushVoteResult(id int, result float64) error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package api
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"go.uber.org/zap"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -14,19 +16,21 @@ type Client struct {
|
|||||||
URL string
|
URL string
|
||||||
Key string
|
Key string
|
||||||
client *http.Client
|
client *http.Client
|
||||||
|
log *zap.SugaredLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(url, key string) *Client {
|
func New(url, key string, log *zap.SugaredLogger) *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
URL: strings.TrimSuffix(url, "/"),
|
URL: strings.TrimSuffix(url, "/"),
|
||||||
Key: key,
|
Key: key,
|
||||||
client: &http.Client{Timeout: time.Second * 2},
|
client: &http.Client{Timeout: time.Second * 2},
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Issue(id uint64) (*Issue, error) {
|
func (c *Client) Issue(id int) (*Issue, error) {
|
||||||
var resp IssueResponse
|
var resp IssueResponse
|
||||||
err := c.sendJSONRequest(http.MethodGet, "/issues/"+strconv.FormatUint(id, 10), nil, &resp)
|
_, err := c.sendJSONRequest(http.MethodGet, "/issues/"+strconv.Itoa(id), nil, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -36,31 +40,59 @@ func (c *Client) Issue(id uint64) (*Issue, error) {
|
|||||||
return resp.Issue, nil
|
return resp.Issue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) sendRequest(method, path string, body io.Reader) (*http.Response, error) {
|
func (c *Client) UpdateIssue(id int, issue *Issue) error {
|
||||||
|
st, err := c.sendJSONRequest(http.MethodPut, "/issues/"+strconv.Itoa(id), IssueResponse{Issue: issue}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if st != http.StatusOK {
|
||||||
|
return errors.New("unexpected status code: " + strconv.Itoa(st))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendRequest(method, path string, headers map[string]string, body io.Reader) (*http.Response, error) {
|
||||||
req, err := http.NewRequest(method, c.URL+path, body)
|
req, err := http.NewRequest(method, c.URL+path, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Set("X-Redmine-API-Key", c.Key)
|
req.Header.Set("X-Redmine-API-Key", c.Key)
|
||||||
|
for name, value := range headers {
|
||||||
|
req.Header.Set(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
return c.client.Do(req)
|
return c.client.Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) sendJSONRequest(method, path string, body, out interface{}) error {
|
func (c *Client) sendJSONRequest(method, path string, body, out interface{}) (int, error) {
|
||||||
var buf io.Reader
|
var buf io.Reader
|
||||||
if body != nil {
|
if body != nil {
|
||||||
data, err := json.Marshal(body)
|
data, err := json.Marshal(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, err
|
||||||
}
|
}
|
||||||
buf = bytes.NewBuffer(data)
|
buf = bytes.NewBuffer(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.sendRequest(method, path+".json", buf)
|
path = path + ".json"
|
||||||
|
c.log.Debugf("[Redmine Request] %s %s; body: %s", method, path, func() string {
|
||||||
|
if buf != nil {
|
||||||
|
return buf.(*bytes.Buffer).String()
|
||||||
|
}
|
||||||
|
return "<nil>"
|
||||||
|
}())
|
||||||
|
|
||||||
|
resp, err := c.sendRequest(method, path, map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
return json.NewDecoder(resp.Body).Decode(out)
|
if out != nil {
|
||||||
|
return resp.StatusCode, json.NewDecoder(resp.Body).Decode(out)
|
||||||
|
}
|
||||||
|
c.log.Debugf("[Redmine Response] code: %d; body: %#v", resp.StatusCode, out)
|
||||||
|
return resp.StatusCode, nil
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrorResponse struct {
|
type ErrorResponse struct {
|
||||||
Errors []string `json:"errors"`
|
Errors []string `json:"errors,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ErrorResponse) Error() string {
|
func (e ErrorResponse) Error() string {
|
||||||
@ -34,26 +36,49 @@ type CustomField struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Issue struct {
|
type Issue struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id,omitempty"`
|
||||||
Project NameWithID `json:"project"`
|
Project *NameWithID `json:"project,omitempty"`
|
||||||
Tracker NameWithID `json:"tracker"`
|
Tracker *NameWithID `json:"tracker,omitempty"`
|
||||||
Status NameWithID `json:"status"`
|
Status *NameWithID `json:"status,omitempty"`
|
||||||
Priority NameWithID `json:"priority"`
|
Priority *NameWithID `json:"priority,omitempty"`
|
||||||
Author NameWithID `json:"author"`
|
Author *NameWithID `json:"author,omitempty"`
|
||||||
AssignedTo NameWithID `json:"assigned_to"`
|
AssignedTo *NameWithID `json:"assigned_to,omitempty"`
|
||||||
Parent NameWithID `json:"parent"`
|
Parent *NameWithID `json:"parent,omitempty"`
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject,omitempty"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description,omitempty"`
|
||||||
StartDate interface{} `json:"start_date"`
|
StartDate interface{} `json:"start_date,omitempty"`
|
||||||
DueDate interface{} `json:"due_date"`
|
DueDate interface{} `json:"due_date,omitempty"`
|
||||||
DoneRatio int `json:"done_ratio"`
|
DoneRatio int `json:"done_ratio,omitempty"`
|
||||||
IsPrivate bool `json:"is_private"`
|
IsPrivate bool `json:"is_private,omitempty"`
|
||||||
EstimatedHours interface{} `json:"estimated_hours"`
|
EstimatedHours Hours `json:"estimated_hours,omitempty"`
|
||||||
TotalEstimatedHours interface{} `json:"total_estimated_hours"`
|
TotalEstimatedHours Hours `json:"total_estimated_hours,omitempty"`
|
||||||
SpentHours float64 `json:"spent_hours"`
|
SpentHours Hours `json:"spent_hours,omitempty"`
|
||||||
TotalSpentHours float64 `json:"total_spent_hours"`
|
TotalSpentHours Hours `json:"total_spent_hours,omitempty"`
|
||||||
CustomFields []CustomField `json:"custom_fields"`
|
CustomFields []CustomField `json:"custom_fields,omitempty"`
|
||||||
CreatedOn time.Time `json:"created_on"`
|
CreatedOn *time.Time `json:"created_on,omitempty"`
|
||||||
UpdatedOn time.Time `json:"updated_on"`
|
UpdatedOn *time.Time `json:"updated_on,omitempty"`
|
||||||
ClosedOn interface{} `json:"closed_on"`
|
ClosedOn interface{} `json:"closed_on,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hours string
|
||||||
|
|
||||||
|
func (h Hours) MarshalJSON() ([]byte, error) {
|
||||||
|
f, err := strconv.ParseFloat(string(h), 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []byte(strconv.FormatFloat(f, 'f', 2, 64)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hours) UnmarshalJSON(b []byte) error {
|
||||||
|
f, err := strconv.ParseFloat(string(b), 64)
|
||||||
|
if err != nil {
|
||||||
|
var output string
|
||||||
|
defer func() {
|
||||||
|
*h = Hours(output)
|
||||||
|
}()
|
||||||
|
return json.Unmarshal(b, &output)
|
||||||
|
}
|
||||||
|
*h = Hours(strconv.FormatFloat(f, 'f', 2, 64))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/iface"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/iface"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/null"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/null"
|
||||||
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/redmine/api"
|
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/redmine/api"
|
||||||
|
"go.uber.org/zap"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -15,10 +16,12 @@ var issueNumberMatcher = regexp.MustCompile(`(?m)\/issues\/(\d+)`)
|
|||||||
var sprintNames = []string{"sprint", "спринт"}
|
var sprintNames = []string{"sprint", "спринт"}
|
||||||
|
|
||||||
type Redmine struct {
|
type Redmine struct {
|
||||||
api *api.Client
|
api *api.Client
|
||||||
|
log *zap.SugaredLogger
|
||||||
|
spFieldName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(params map[string]interface{}) iface.Integration {
|
func New(params map[string]interface{}, log *zap.SugaredLogger) iface.Integration {
|
||||||
uri, ok := params["url"]
|
uri, ok := params["url"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return null.New()
|
return null.New()
|
||||||
@ -35,28 +38,57 @@ func New(params map[string]interface{}) iface.Integration {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return null.New()
|
return null.New()
|
||||||
}
|
}
|
||||||
return &Redmine{api: api.New(uriString, keyString)}
|
var spField string
|
||||||
|
if spFieldName, ok := params["sp_field"]; ok {
|
||||||
|
if strField, ok := spFieldName.(string); ok {
|
||||||
|
spField = strField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Redmine{api: api.New(uriString, keyString, log), log: log, spFieldName: spField}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Redmine) GetTaskInfoText(input string) string {
|
func (r *Redmine) GetTaskInfo(input string) (int, string) {
|
||||||
taskNumber := r.getTaskNumber(input)
|
taskNumber := r.getTaskNumber(input)
|
||||||
if taskNumber == 0 {
|
if taskNumber == 0 {
|
||||||
return ""
|
r.log.Debugf("[Redmine] Cannot extract task number from %s", input)
|
||||||
|
return 0, ""
|
||||||
}
|
}
|
||||||
task, err := r.api.Issue(taskNumber)
|
task, err := r.api.Issue(taskNumber)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
r.log.Errorf("[Redmine] Cannot get Redmine issue %d: %s", taskNumber, err)
|
||||||
|
return 0, ""
|
||||||
}
|
}
|
||||||
sprint := r.getSprint(task.CustomFields)
|
sprint := r.getSprint(task.CustomFields)
|
||||||
if sprint != "" {
|
if sprint != "" {
|
||||||
sprint = fmt.Sprintf(" - **%s**", sprint)
|
sprint = fmt.Sprintf(" - _%s_", sprint)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("[(%s) %s: %s](%s/issues/%d)%s",
|
taskInfo := strings.ReplaceAll(fmt.Sprintf("_(%s) %s_: [%s](%s/issues/%d)%s",
|
||||||
task.Project.Name, task.Tracker.Name, task.Subject, r.api.URL, taskNumber, sprint)
|
task.Project.Name, task.Tracker.Name, task.Subject, r.api.URL, taskNumber, sprint), "*", "")
|
||||||
|
return task.ID, "\n" + taskInfo + ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Redmine) getTaskNumber(input string) uint64 {
|
func (r *Redmine) PushVoteResult(id int, result float64) error {
|
||||||
num, err := strconv.ParseUint(input, 10, 64)
|
task, err := r.api.Issue(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
issue := &api.Issue{
|
||||||
|
EstimatedHours: api.Hours(strconv.FormatFloat(result*8, 'f', 0, 64)),
|
||||||
|
}
|
||||||
|
if r.spFieldName != "" {
|
||||||
|
for _, field := range task.CustomFields {
|
||||||
|
field := field
|
||||||
|
if field.Name == r.spFieldName {
|
||||||
|
field.Value = strconv.FormatFloat(result, 'f', 1, 64)
|
||||||
|
issue.CustomFields = []api.CustomField{field}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.api.UpdateIssue(id, issue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Redmine) getTaskNumber(input string) int {
|
||||||
|
num, err := strconv.Atoi(input)
|
||||||
if err == nil && num > 0 {
|
if err == nil && num > 0 {
|
||||||
return num
|
return num
|
||||||
}
|
}
|
||||||
@ -64,7 +96,7 @@ func (r *Redmine) getTaskNumber(input string) uint64 {
|
|||||||
if len(matches) < 2 {
|
if len(matches) < 2 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
number, err := strconv.ParseUint(matches[1], 10, 64)
|
number, err := strconv.Atoi(matches[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -28,3 +28,15 @@ should_send_estimated_hours_redmine: "🕑 Do you want the poker result to be co
|
|||||||
setup_done: "✔️ Done, now your chat is connected to the bot!\nUse the command /poll@vegapokerbot in the connected chat to start a vote."
|
setup_done: "✔️ Done, now your chat is connected to the bot!\nUse the command /poll@vegapokerbot in the connected chat to start a vote."
|
||||||
yes: "✔️ Yes"
|
yes: "✔️ Yes"
|
||||||
no: "✖️ No"
|
no: "✖️ No"
|
||||||
|
poker_start: "@{{.Name}} started poker{{.DotOrColon}}{{.Subject}}"
|
||||||
|
poker_ended: "Voting was finished by @{{.Name}}"
|
||||||
|
poker_ended_no_results: "_There were no votes._"
|
||||||
|
no_votes_yet: "_There are no votes yet..._"
|
||||||
|
voted: "Voted:"
|
||||||
|
voter: "• 👨💻 {{.Name}}"
|
||||||
|
vote_result: "• 👨💻 {{.Name}}: {{.Vote}}"
|
||||||
|
poker_results: "Result:\n• Max: {{.Max}}\n• Min: {{.Min}}\n• Average: {{.Avg}}\n• Rounded to half: {{.RoundHalf}}"
|
||||||
|
poker_redmine_cannot_send: "😢 _Cannot send results to Redmine._"
|
||||||
|
poker_redmine_sent: "📕 _Results were sent to Redmine._"
|
||||||
|
finish_vote: "🏁 Finish vote"
|
||||||
|
cannot_find_poll: "Cannot find this poll. Please start it again if you need it."
|
||||||
|
@ -11,6 +11,7 @@ unknown_command: "❔ Неизвестная команда. Используй
|
|||||||
help_output: "📄 Доступные команды:\n\n• /start - регистрация в боте;\n• /poll@vegapokerbot <task> - запускает poker в группе, работает только в подключенных группах;\n• /help - выводит эту справку."
|
help_output: "📄 Доступные команды:\n\n• /start - регистрация в боте;\n• /poll@vegapokerbot <task> - запускает poker в группе, работает только в подключенных группах;\n• /help - выводит эту справку."
|
||||||
choose_at_least_one: "❌ Вы должны выбрать хотя бы одного участника."
|
choose_at_least_one: "❌ Вы должны выбрать хотя бы одного участника."
|
||||||
ask_for_redmine: "📕 Настроить интеграцию с Redmine?"
|
ask_for_redmine: "📕 Настроить интеграцию с Redmine?"
|
||||||
|
send_result_redmine: "📕 Отправить результат в Redmine"
|
||||||
redmine_will_not_be_configured: "👌 Интеграция с Redmine не будет настроена. Вы не сможете отправлять результаты poker в Redmine или получать информацию о задачах."
|
redmine_will_not_be_configured: "👌 Интеграция с Redmine не будет настроена. Вы не сможете отправлять результаты poker в Redmine или получать информацию о задачах."
|
||||||
redmine_hours_will_not_be_configured: "👌 Оценка временных затрат не будет передаваться в Redmine."
|
redmine_hours_will_not_be_configured: "👌 Оценка временных затрат не будет передаваться в Redmine."
|
||||||
redmine_hours_will_be_configured: "👌 Оценка временных затрат будет передаваться в Redmine."
|
redmine_hours_will_be_configured: "👌 Оценка временных затрат будет передаваться в Redmine."
|
||||||
@ -28,3 +29,15 @@ should_send_estimated_hours_redmine: "🕑 Передавать в поле *О
|
|||||||
setup_done: "✔️ Готово, чат подключен к боту!\nИспользуйте команду /poll@vegapokerbot в подключенном чате для запуска голосования."
|
setup_done: "✔️ Готово, чат подключен к боту!\nИспользуйте команду /poll@vegapokerbot в подключенном чате для запуска голосования."
|
||||||
yes: "✔️ Да"
|
yes: "✔️ Да"
|
||||||
no: "✖️ Нет"
|
no: "✖️ Нет"
|
||||||
|
poker_start: "@{{.Name}} запустил покер{{.DotOrColon}}{{.Subject}}"
|
||||||
|
no_votes_yet: "_Пока еще никто не проголосовал..._"
|
||||||
|
poker_ended: "Голосование было завершено @{{.Name}}"
|
||||||
|
poker_ended_no_results: "_Никто не проголосовал._"
|
||||||
|
voted: "Проголосовали:"
|
||||||
|
voter: "• 👨💻 {{.Name}}"
|
||||||
|
vote_result: "• 👨💻 {{.Name}}: {{.Vote}}"
|
||||||
|
poker_results: "Результат:\n• Максимум: {{.Max}}\n• Минимум: {{.Min}}\n• Среднее: {{.Avg}}\n• Округление до половины: {{.RoundHalf}}"
|
||||||
|
poker_redmine_cannot_send: "😢 _Не удалось отправить результаты в Redmine._"
|
||||||
|
poker_redmine_sent: "📕 _Результаты были отправлены в Redmine._"
|
||||||
|
finish_vote: "🏁 Завершить голосование"
|
||||||
|
cannot_find_poll: "Не удалось найти это голосование. Запустите его заново если оно вам нужно."
|
||||||
|
@ -4,7 +4,7 @@ import "github.com/nicksnyder/go-i18n/v2/i18n"
|
|||||||
|
|
||||||
type Localizer interface {
|
type Localizer interface {
|
||||||
Message(string) string
|
Message(string) string
|
||||||
Template(string, map[string]interface{}) string
|
Template(string, interface{}) string
|
||||||
}
|
}
|
||||||
|
|
||||||
type localizer struct {
|
type localizer struct {
|
||||||
@ -15,7 +15,7 @@ func (l *localizer) Message(str string) string {
|
|||||||
return l.Template(str, nil)
|
return l.Template(str, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localizer) Template(str string, tpl map[string]interface{}) string {
|
func (l *localizer) Template(str string, tpl interface{}) string {
|
||||||
return l.loc.MustLocalize(&i18n.LocalizeConfig{
|
return l.loc.MustLocalize(&i18n.LocalizeConfig{
|
||||||
MessageID: str,
|
MessageID: str,
|
||||||
TemplateData: tpl,
|
TemplateData: tpl,
|
||||||
|
Loading…
Reference in New Issue
Block a user