- router implementation

This commit is contained in:
naudachu 2023-11-27 17:44:32 +05:00
parent 19f6064066
commit 2be4b1dfd4
3 changed files with 228 additions and 91 deletions

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"os/signal" "os/signal"
"ticket-pimp/client/discord/discord_handler" "ticket-pimp/client/discord/discord_handler"
"ticket-pimp/client/discord/discord_router"
"ticket-pimp/internal/controller" "ticket-pimp/internal/controller"
"ticket-pimp/internal/domain" "ticket-pimp/internal/domain"
@ -19,7 +20,7 @@ var (
repoType string = "repo_type" repoType string = "repo_type"
projectRepo string = "project_repo" projectRepo string = "project_repo"
buildRepo string = "build_repo" buildRepo string = "build_repo"
nameOption string = "repo_name" nameOption string = "name"
tagsPreset = [3]discordgo.ForumTag{ tagsPreset = [3]discordgo.ForumTag{
{ {
Name: "В работе", Name: "В работе",
@ -135,79 +136,6 @@ type DiscordOptions struct {
Controller *controller.WorkflowController Controller *controller.WorkflowController
} }
// Моментальный ответ для избежания столкновения с протуханием токена
func initialResponse(s *discordgo.Session, i *discordgo.InteractionCreate) {
initialResponse := discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsEphemeral,
Content: "👩‍🍳 Cooking your query..",
},
}
s.InteractionRespond(i.Interaction, &initialResponse)
}
// Определяем канал и реджектим запрос, если пишут в лс:
func isRejected(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
dchan, err := s.Channel(i.ChannelID)
if err != nil {
return true
}
if dchan.Type == discordgo.ChannelTypeDM {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Yo, fella! I'm not working in private!",
},
})
return true
}
return false
}
func route(s *discordgo.Session, i *discordgo.InteractionCreate, h discord_handler.Handler) {
// initialResponse(s, i)
if isRejected(s, i) {
return
}
// Определяем тип взаимодействия и хэндлим правильной функцией:
switch i.Type {
case discordgo.InteractionApplicationCommand:
switch i.ApplicationCommandData().Name {
case "ping":
h.Ping(s, i)
case "project":
h.CreateProject(s, i)
case "info":
h.ProjectInfo(s, i)
case "repo":
h.CreateGit(s, i)
case "folder":
h.CreateFolder(s, i)
case "init_project":
h.InitChannelAsProject(s, i)
case "coda_ticket":
h.CreateCoda(s, i)
}
case discordgo.InteractionMessageComponent:
switch i.MessageComponentData().CustomID {
case "task_start":
h.HandleTaskButtons(s, i)
case "task_close":
h.HandleTaskButtons(s, i)
}
}
}
func updateForum(conf *domain.Config, s *discordgo.Session) ([]discordgo.ForumTag, error) { func updateForum(conf *domain.Config, s *discordgo.Session) ([]discordgo.ForumTag, error) {
log.Println("Updating forum chan...") log.Println("Updating forum chan...")
@ -293,14 +221,33 @@ func Run(conf *domain.Config, opts DiscordOptions) error {
external.NewDummyClient(conf.Telegram), external.NewDummyClient(conf.Telegram),
) )
r := discord_router.NewApp(s)
var commonMw = []discord_router.Middleware{
h.WithInitialResponse,
h.RejectPM,
}
// Handle commands
r.
Route("ping", r.Wrapped(h.Ping, commonMw...)).
Route("project", r.Wrapped(h.CreateProject, commonMw...)).
Route("info", r.Wrapped(h.ProjectInfo, commonMw...)).
Route("repo", r.Wrapped(h.CreateGit, commonMw...)).
Route("folder", r.Wrapped(h.CreateFolder, commonMw...)).
Route("init_project", r.Wrapped(h.InitChannelAsProject, commonMw...)).
Route("coda_ticket", r.Wrapped(h.CreateCoda, commonMw...))
// Handle components
r.
Route("task_start", h.HandleTaskButtons).
Route("task_close", h.HandleTaskButtons)
// Add posts listener // Add posts listener
s.AddHandler(h.ListenPosts) s.AddHandler(h.ListenPosts)
// Add interactions handlers // Add interactions handlers
s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { s.AddHandler(r.Serve)
route(s, i, *h)
})
// session opening // session opening
if err := s.Open(); err != nil { if err := s.Open(); err != nil {

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"ticket-pimp/adapters" "ticket-pimp/adapters"
"ticket-pimp/client/discord/discord_router"
"ticket-pimp/internal/controller" "ticket-pimp/internal/controller"
"ticket-pimp/internal/domain" "ticket-pimp/internal/domain"
"ticket-pimp/internal/helpers" "ticket-pimp/internal/helpers"
@ -31,6 +32,69 @@ func New(
} }
} }
func (h *Handler) RejectPM(f discord_router.HandlerFunc) discord_router.HandlerFunc {
return func(s *discordgo.Session, i *discordgo.InteractionCreate) {
dchan, err := s.Channel(i.ChannelID)
if err != nil {
log.Println(err)
return
}
if dchan.Type != discordgo.ChannelTypeDM {
f(s, i)
return
}
respondWithReject(s, i)
}
}
func respondWithReject(s *discordgo.Session, i *discordgo.InteractionCreate) {
var content = "Yo, fella! I'm not working in private!"
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: content,
},
})
if err == nil {
return
}
_, err = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
Content: &content,
})
if err != nil {
log.Printf("unable to edit answer with unknown error %v", err)
}
}
// Моментальный ответ для избежания столкновения с протуханием токена
func (h *Handler) WithInitialResponse(f discord_router.HandlerFunc) discord_router.HandlerFunc {
return func(s *discordgo.Session, i *discordgo.InteractionCreate) {
initialResponse := discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsEphemeral,
Content: "👩‍🍳 Cooking your query..",
},
}
err := s.InteractionRespond(i.Interaction, &initialResponse)
if err != nil {
log.Println(err)
}
f(s, i)
}
}
func (h *Handler) SetAvailableTags(tags []discordgo.ForumTag) { func (h *Handler) SetAvailableTags(tags []discordgo.ForumTag) {
h.tags = append(h.tags, tags...) h.tags = append(h.tags, tags...)
} }
@ -45,9 +109,10 @@ func (h *Handler) Ping(s *discordgo.Session, i *discordgo.InteractionCreate) {
i.ChannelID, i.ChannelID,
) )
_, err := s.FollowupMessageCreate(i.Interaction, false, &discordgo.WebhookParams{ _, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
Content: content, Content: &content,
}) })
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
@ -65,8 +130,10 @@ func (h *Handler) ListenPosts(s *discordgo.Session, th *discordgo.ThreadCreate)
return return
} }
// Get all messages from the channel:
msgs, _ := s.ChannelMessages(th.ID, 1, "", "", "") msgs, _ := s.ChannelMessages(th.ID, 1, "", "", "")
// Take the first one:
msg, _ := s.ChannelMessage(th.ID, msgs[0].ID) msg, _ := s.ChannelMessage(th.ID, msgs[0].ID)
if msg.Author.ID == s.State.User.ID { if msg.Author.ID == s.State.User.ID {
@ -88,8 +155,14 @@ func (h *Handler) ListenPosts(s *discordgo.Session, th *discordgo.ThreadCreate)
} }
// Отредактировать Thread name как для задачи // Отредактировать Thread name как для задачи
appliedTags := []string{
h.conf.Tags[domain.NewTaskState()],
// h.tags[2].ID,
}
_, err = s.ChannelEditComplex(th.ID, &discordgo.ChannelEdit{ _, err = s.ChannelEditComplex(th.ID, &discordgo.ChannelEdit{
Name: fmt.Sprintf("Task ID: %d, by %s", t.ID, t.Creator), Name: fmt.Sprintf("Task ID: %d, by %s", t.ID, t.Creator),
AppliedTags: &appliedTags,
}) })
if err != nil { if err != nil {
log.Printf("th edition is not complete: %v", err) log.Printf("th edition is not complete: %v", err)
@ -132,6 +205,12 @@ func (h *Handler) ListenPosts(s *discordgo.Session, th *discordgo.ThreadCreate)
// .. handler function to work with the Action Buttons over a task // .. handler function to work with the Action Buttons over a task
func (h *Handler) HandleTaskButtons(s *discordgo.Session, i *discordgo.InteractionCreate) { func (h *Handler) HandleTaskButtons(s *discordgo.Session, i *discordgo.InteractionCreate) {
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{},
})
_ = err
// Get assignee value; --------------------------------------------------------------------------------- // Get assignee value; ---------------------------------------------------------------------------------
user := i.Member.User.Mention() user := i.Member.User.Mention()
@ -209,7 +288,6 @@ func (h *Handler) HandleTaskButtons(s *discordgo.Session, i *discordgo.Interacti
log.Printf("th start message edition is not complete: %v", err) log.Printf("th start message edition is not complete: %v", err)
} }
// [ ] Устанавливаем тэги статуса на тред ---------------------------------------------------------------------
err = h.setFlag(s, i, &tag) err = h.setFlag(s, i, &tag)
if err != nil { if err != nil {
log.Printf("error while `start` tag setting: %v", err) log.Printf("error while `start` tag setting: %v", err)
@ -291,12 +369,14 @@ func (h *Handler) CreateFolder(s *discordgo.Session, i *discordgo.InteractionCre
- writed git link to db; - writed git link to db;
*/ */
func (h *Handler) CreateGit(s *discordgo.Session, i *discordgo.InteractionCreate) { func (h *Handler) CreateGit(s *discordgo.Session, i *discordgo.InteractionCreate) {
const ( const (
repoType = "repo_type" typeOfRepo = "repo_type"
projectRepo = "project_repo" projectRepoType = "project_repo"
buildRepo = "build_repo" buildRepoType = "build_repo"
nameOption = "repo_name" nameOption = "repo_name"
) )
// Моментальный ответ для избежания столкновения с протуханием токена // Моментальный ответ для избежания столкновения с протуханием токена
initialResponse := discordgo.InteractionResponse{ initialResponse := discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource, Type: discordgo.InteractionResponseChannelMessageWithSource,
@ -322,18 +402,22 @@ func (h *Handler) CreateGit(s *discordgo.Session, i *discordgo.InteractionCreate
// Creating request: // Creating request:
var req controller.GitRequest var req controller.GitRequest
name, insertedValueNotNil := optionMap[nameOption] name, insertedValueNotNil := optionMap[nameOption]
isBuild := optionMap[repoType]
isBuild := optionMap[typeOfRepo]
switch isBuild.StringValue() { switch isBuild.StringValue() {
case buildRepo: case buildRepoType:
req.IsBuildGit = true req.IsBuildGit = true
case projectRepo: case projectRepoType:
req.IsBuildGit = false req.IsBuildGit = false
} }
dchan, err := s.Channel(i.ChannelID)
dchan, err := s.Channel(i.ChannelID)
if err != nil { if err != nil {
log.Printf("error while identifying channel: %v", err) h.defaultFollowUp("error while identifying channel: %v", s, i)
return
} else { } else {
if dchan.ParentID == h.conf.IsProjectChannel { if dchan.ParentID == h.conf.IsProjectChannel {
@ -530,3 +614,35 @@ func (h *Handler) CreateProject(s *discordgo.Session, i *discordgo.InteractionCr
h.defaultFollowUp(result, s, i) h.defaultFollowUp(result, s, i)
} }
// CreateCoda
/*
- sends request to Coda.io;
*/
func (h *Handler) CreateCoda(s *discordgo.Session, i *discordgo.InteractionCreate) {
// Get channel from the request
dchan, err := s.Channel(i.ChannelID)
if err != nil {
h.defaultFollowUp("unable to get channel from the message", s, i)
return
}
if dchan.ParentID != h.conf.IsProjectChannel {
h.defaultFollowUp("This channel is not at the project's group", s, i)
return
}
//[ ] достать проект из базы и послать в коду
result, err := h.controller.CreateCoda(i.GuildID, dchan.ID)
if err != nil {
h.defaultFollowUp(fmt.Sprintf("unable to create coda: %v", err), s, i)
return
}
if err != nil {
result += fmt.Sprintf("\nexecuted w/ error: %v", err)
}
h.defaultFollowUp(result, s, i)
}

View File

@ -0,0 +1,74 @@
package discord_router
import "github.com/bwmarrin/discordgo"
type HandlerFunc func(*discordgo.Session, *discordgo.InteractionCreate)
type RouteEntry struct {
CommandName string
Handler HandlerFunc
}
func (re *RouteEntry) Match(i *discordgo.InteractionCreate) bool {
switch i.Type {
case discordgo.InteractionApplicationCommand:
if i.ApplicationCommandData().Name != re.CommandName {
return false
}
case discordgo.InteractionMessageComponent:
if i.MessageComponentData().CustomID != re.CommandName {
return false
}
}
return true
}
type Router struct {
session *discordgo.Session
routes []RouteEntry
}
func NewApp(s *discordgo.Session) *Router {
return &Router{
session: s,
}
}
func (r *Router) Route(cmd string, handlerFunc HandlerFunc) *Router {
r.routes = append(r.routes, RouteEntry{
CommandName: cmd,
Handler: handlerFunc,
})
return r
}
func (r *Router) Serve(s *discordgo.Session, i *discordgo.InteractionCreate) {
for _, e := range r.routes {
ok := e.Match(i)
if ok {
e.Handler(s, i)
}
}
//[ ] Is there something like 404?!
}
type Middleware func(HandlerFunc) HandlerFunc
func (r *Router) Wrapped(f HandlerFunc, m ...Middleware) HandlerFunc {
if len(m) < 1 {
return f
}
wrapped := f
// loop in reverse to preserve middleware order
for i := len(m) - 1; i >= 0; i-- {
wrapped = m[i](wrapped)
}
return wrapped
}