From 0033c68067f52912e8c9dc20c43fd4f6678ebb7e Mon Sep 17 00:00:00 2001 From: Luther Wen Xu Date: Thu, 10 Oct 2019 21:59:00 +0800 Subject: [PATCH] go: Implement vote ending --- GoBot/db_vote.go | 58 +++++++++++++++++++++++ GoBot/main.go | 3 ++ GoBot/trust.go | 11 +++++ GoBot/vote.go | 28 +++++++++-- GoBot/vote_result.go | 109 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 GoBot/trust.go create mode 100644 GoBot/vote_result.go diff --git a/GoBot/db_vote.go b/GoBot/db_vote.go index 6562f9b..d48beaa 100644 --- a/GoBot/db_vote.go +++ b/GoBot/db_vote.go @@ -56,6 +56,40 @@ func getVoteFromMessageID(msgID string) (int, error) { return 0, errNotFound } +func getMessageIDFromVote(voteID int) (string, error) { + rows, err := db.Query("SELECT messageId FROM vote WHERE id=?", voteID) + if err != nil { + return "", err + } + defer rows.Close() + if rows.Next() { + var id string + err := rows.Scan(&id) + if err != nil { + return "", err + } + return id, nil + } + return "", errNotFound +} + +func getVoteType(voteID int) (string, error) { + rows, err := db.Query("SELECT type FROM vote WHERE id=?", voteID) + if err != nil { + return "", err + } + defer rows.Close() + if rows.Next() { + var messageType string + err := rows.Scan(&messageType) + if err != nil { + return "", err + } + return messageType, nil + } + return "", errNotFound +} + func updateVote(voteID int, userID string, voteValue int) error { if voteValue == forceRejectionVote { //Check if they used it within a month. @@ -79,3 +113,27 @@ func updateVote(voteID int, userID string, voteValue int) error { _, err = db.Exec("REPLACE INTO choices (voteId, userId, date, value) VALUES (?, ?, ?, ?)", voteID, userID, time.Now(), voteValue) return err } + +func finishVote(voteID int) error { + _, err := db.Exec("UPDATE vote SET finished=true WHERE id=?", voteID) + return err +} + +func getAllVoteChoices(voteID int) ([]voteChoice, error) { + rows, err := db.Query("SELECT userId, value FROM choices WHERE voteId=?", voteID) + if err != nil { + return []voteChoice{}, err + } + defer rows.Close() + array := make([]voteChoice, 0) + for rows.Next() { + var userID string + var value int + rows.Scan(&userID, &value) + array = append(array, voteChoice{ + UserID: userID, + Value: value, + }) + } + return array, nil +} diff --git a/GoBot/main.go b/GoBot/main.go index b6ec014..c7ac7cc 100644 --- a/GoBot/main.go +++ b/GoBot/main.go @@ -19,13 +19,16 @@ func main() { if err != nil { panic(err) } + dg.AddHandler(messageCreate) dg.AddHandler(checkForVote) + if err := dg.Open(); err != nil { panic(err) } fmt.Println("Bot is now running. Press CTRL-C to exit.") + go listenToVoteFinishes(dg) sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) <-sc diff --git a/GoBot/trust.go b/GoBot/trust.go new file mode 100644 index 0000000..b7eea49 --- /dev/null +++ b/GoBot/trust.go @@ -0,0 +1,11 @@ +package main + +func getTrust(discordID string) int { + //TODO This is a stub. Everyone has same weight for now. + return 1 +} + +func getTotalTrust() int { + //TODO This is a stub. It returns 1 for easier testing for now. + return 1 +} diff --git a/GoBot/vote.go b/GoBot/vote.go index 01d7f9b..3ea69c7 100644 --- a/GoBot/vote.go +++ b/GoBot/vote.go @@ -8,7 +8,20 @@ import ( "github.com/bwmarrin/discordgo" ) +type voteType struct { + EmbedBuilder func(id int, name string) *discordgo.MessageEmbed + ResultHandler func(s *discordgo.Session, id int, name string, isPositive bool) +} + +var voteTypes = map[string]voteType{ + "custom": voteType{ + EmbedBuilder: createCustomEmbed, + ResultHandler: announceCustomResult, + }, +} + const voteChannel = "627164246056239104" +const announceCustomChannel = "627165467269922864" const ( emojiOne = "1⃣" @@ -81,6 +94,8 @@ func checkForVote(s *discordgo.Session, r *discordgo.MessageReactionAdd) { } sendPrivateMessage(s, r.UserID, "您投票已成功。\nYou have voted successfully.\n名字Vote Name: "+voteName+"\n目前投的票: :"+r.Emoji.Name+":") + + checkForVoteResult(s, voteID) } func voteSuggestion(s *discordgo.Session, m *discordgo.MessageCreate) { @@ -112,10 +127,7 @@ func voteSuggestion(s *discordgo.Session, m *discordgo.MessageCreate) { } auditLog(s, fmt.Sprintf("Vote ID %d has been created by <@%s>.", id, m.Author.ID)) s.ChannelMessageEdit(voteChannel, msg.ID, "") - s.ChannelMessageEditEmbed( - voteChannel, msg.ID, - newEmbed().SetColour(0x00FFFF).SetTitle("自定义投票Custom Vote").AddField("ID", strconv.Itoa(id)).AddField("内容Content", args[1]).Build(), - ) + s.ChannelMessageEditEmbed(voteChannel, msg.ID, createCustomEmbed(id, args[1])) s.MessageReactionAdd(voteChannel, msg.ID, emojiOne) s.MessageReactionAdd(voteChannel, msg.ID, emojiTwo) s.MessageReactionAdd(voteChannel, msg.ID, emojiThree) @@ -125,3 +137,11 @@ func voteSuggestion(s *discordgo.Session, m *discordgo.MessageCreate) { sendPrivateMessage(s, m.Author.ID, "未知投票种类:"+args[0]+"\nUnknown vote type: "+args[0]) } } + +func createCustomEmbed(id int, name string) *discordgo.MessageEmbed { + return newEmbed().SetColour(0x00FFFF).SetTitle("自定义投票Custom Vote").AddField("ID", strconv.Itoa(id)).AddField("内容Content", name).Build() +} + +func announceCustomResult(s *discordgo.Session, id int, name string, isPositive bool) { + s.ChannelMessageSendEmbed(announceCustomChannel, showVoteStatus(createCustomEmbed(id, name), isPositive)) +} diff --git a/GoBot/vote_result.go b/GoBot/vote_result.go new file mode 100644 index 0000000..00cdb45 --- /dev/null +++ b/GoBot/vote_result.go @@ -0,0 +1,109 @@ +package main + +import ( + "fmt" + "sync" + "time" + + "github.com/bwmarrin/discordgo" +) + +type voteChoice struct { + UserID string + Value int +} + +type confirmedResult struct { + VoteID int + IsPositive bool +} + +var voteMutex sync.Mutex +var toAnnounceResultList []confirmedResult + +func listenToVoteFinishes(s *discordgo.Session) { + for { + voteMutex.Lock() + for _, v := range toAnnounceResultList { + auditLog(s, fmt.Sprintf("Announcing the result of vote %d.", v.VoteID)) + if err := finishVote(v.VoteID); err != nil { + auditLog(s, fmt.Sprintf("Error while finishing vote %d.", v.VoteID)) + } + voteType, _ := getVoteType(v.VoteID) + voteName, _ := getVoteName(v.VoteID) + messageID, _ := getMessageIDFromVote(v.VoteID) + s.ChannelMessageEditEmbed(voteChannel, messageID, showVoteStatus(voteTypes[voteType].EmbedBuilder(v.VoteID, voteName), v.IsPositive)) + voteTypes[voteType].ResultHandler(s, v.VoteID, voteName, v.IsPositive) + } + toAnnounceResultList = toAnnounceResultList[:0] + voteMutex.Unlock() + time.Sleep(60 * time.Second) + } +} + +func checkForVoteResult(s *discordgo.Session, id int) { + votes, err := getAllVoteChoices(id) + if err != nil { + auditLog(s, fmt.Sprintf("Error while calculating vote result for ID %d: %s", id, err.Error())) + return + } + var currentScore, totalTrust float64 + var absoluteRejectionVote bool + for _, vote := range votes { + if vote.Value == forceRejectionVote { + absoluteRejectionVote = true + } + currentScore += float64(vote.Value * getTrust(vote.UserID)) + totalTrust += float64(getTrust(vote.UserID)) + } + + remainingTrust := float64(getTotalTrust()) - totalTrust + lowestPossible := (currentScore + remainingTrust) / float64(getTotalTrust()) + highestPossible := (currentScore + remainingTrust*5) / float64(getTotalTrust()) + + if highestPossible <= 3.5 || absoluteRejectionVote { + //Rejection confirmed. + voteMutex.Lock() + for _, v := range toAnnounceResultList { + if v.VoteID == id { + //It's already queued to be announced in the next announcement cycle. + voteMutex.Unlock() + return + } + } + auditLog(s, fmt.Sprintf("Rejection of vote ID %d has been confirmed.", id)) + toAnnounceResultList = append(toAnnounceResultList, confirmedResult{ + VoteID: id, + IsPositive: false, + }) + voteMutex.Unlock() + } + if lowestPossible >= 3.5 { + //Acceptance confirmed. + voteMutex.Lock() + for _, v := range toAnnounceResultList { + if v.VoteID == id { + //It's already queued to be announced in the next announcement cycle. + voteMutex.Unlock() + return + } + } + auditLog(s, fmt.Sprintf("Acceptance of vote ID %d has been confirmed.", id)) + toAnnounceResultList = append(toAnnounceResultList, confirmedResult{ + VoteID: id, + IsPositive: true, + }) + voteMutex.Unlock() + } +} + +func showVoteStatus(embed *discordgo.MessageEmbed, isPositive bool) *discordgo.MessageEmbed { + if isPositive { + embed.Color = 0x00F000 + embed.Title = "投票通过 Vote Passed" + } else { + embed.Color = 0xF00000 + embed.Title = "投票不通过 Vote Rejected" + } + return embed +}