Compare commits

...

39 Commits

Author SHA1 Message Date
Luther Wen Xu 1ddb32e12b
go: discord/backend: Initialize map to prevent crash 2019-10-20 17:51:32 +07:00
Luther Wen Xu 38e4f1facc
go: discord/modules: Prevent changed votes from causing a vote to pass/reject 2019-10-20 17:25:52 +07:00
Luther Wen Xu c0965b86aa
go: discord/backend: Implement actual trust vote caching 2019-10-20 17:19:14 +07:00
Luther Wen Xu b6685d22bb
go: discord/modules: Do better hashing while obfuscating 2019-10-19 23:01:18 +07:00
Luther Wen Xu 0bec62f96c
go: discord/backend: Add temporary workaround to discordgo's duplication issue 2019-10-19 22:54:18 +07:00
Luther Wen Xu b6feb6a0a2
go: discord/backend: Remove some bias from trust score calculation 2019-10-19 22:48:40 +07:00
Luther Wen Xu 5b33e20640
go: discord/modules: Obfuscate username by default
This allows me to view trust level report without actually knowing the trust level of everyone
2019-10-19 22:47:39 +07:00
Luther Wen Xu ddb7877fca
go: Update discordgo 2019-10-19 22:22:36 +07:00
Luther Wen Xu 6fceb6c50e
go: discord/modules: Fix embed generated by '!votesuggest' command 2019-10-18 23:58:50 +07:00
Luther Wen Xu 430b8be8f5
go: discord/backend: Use RequestGuildMembers as suggested by the Gopher Discord server 2019-10-18 23:56:14 +07:00
Luther Wen Xu ddf9af9557
go: db: Use sqlite3 syntax instead of MySQL extension syntax 2019-10-18 12:29:10 +07:00
Luther Wen Xu 1da6ceb136
go: discord: Rely less on state and more on events 2019-10-18 12:20:26 +07:00
Luther Wen Xu 0bb9092430
go: discord: Better state caching 2019-10-18 12:03:33 +07:00
Luther Wen Xu 7116381000
go: discord/backend: Gracefully handle low global active level 2019-10-18 11:48:50 +07:00
Luther Wen Xu c934af81e2
go: server: Fix unreachable active endpoint 2019-10-18 11:48:14 +07:00
Luther Wen Xu ec43666620
go: discord/backend: Consider active level while calculating trust level 2019-10-18 11:39:20 +07:00
Luther Wen Xu a68565f215
go: discord/modules: Fix '!votesuggest' command 2019-10-18 11:30:30 +07:00
Luther Wen Xu 27158b2afb
go: server: Implement active endpoint 2019-10-17 17:08:57 +07:00
Luther Wen Xu 1963c773d6
go: db: Implement active level 2019-10-17 17:02:35 +07:00
Luther Wen Xu e97bc82606
go: discord/modules: Fix invite colouring 2019-10-17 16:54:12 +07:00
Luther Wen Xu 69235d3f3b
go: db: Fix query of ForceRejection 2019-10-17 16:49:58 +07:00
Luther Wen Xu ea08cbd36e
go: discord/modules: More send to PM channel instead of invalid user 2019-10-17 16:46:34 +07:00
Luther Wen Xu 712af25677
go: discord/modules: Send to PM channel instead of invalid user 2019-10-16 13:42:36 +07:00
Luther Wen Xu 617a131d2d
go: discord/modules: Properly return on error 2019-10-16 13:10:39 +07:00
Luther Wen Xu 68c0afd97c
go: discord/modules: Add limits to change trust 2019-10-16 12:47:36 +07:00
Luther Wen Xu b118fbd477
go: discord/modules: Set limit for validation code length 2019-10-16 12:40:26 +07:00
Luther Wen Xu 34daaa83cb
go: discord/backend: Use Minecraft username as fallback for member search 2019-10-16 12:36:37 +07:00
Luther Wen Xu 7d29b0f923
go: Use actual mention instead of Discord ID in message 2019-10-16 00:25:50 +07:00
Luther Wen Xu 646f904755
go: Do not show admin commands in help message 2019-10-15 23:53:52 +07:00
Luther Wen Xu 093f57b6ee
go: Notify client when user leave voice channel 2019-10-15 23:38:35 +07:00
Luther Wen Xu 4a377f80fc
go: Enforce react by user 2019-10-15 23:35:32 +07:00
Luther Wen Xu d1311d057f
go: Register voice state update listener 2019-10-15 23:29:23 +07:00
Luther Wen Xu b7625cd6ac
go: Add more debug info 2019-10-15 22:58:58 +07:00
Luther Wen Xu 0477b55885
go: Improve performance by using discordgo caching 2019-10-15 19:06:42 +07:00
Luther Wen Xu b788849469
go: Implement '!batchrole' command 2019-10-15 19:01:36 +07:00
Luther Wen Xu e9c303c1da
go: Prettify vote names before sending them 2019-10-15 18:36:29 +07:00
ALI Hamza 42ef07a1b4
go: add help command and refactor commands.go 2019-10-14 19:34:28 +07:00
Luther Wen Xu 5a31d1fe1e
go: Improve Chinese Translation
Courtesy of zeanngew
2019-10-14 19:22:40 +07:00
Luther Wen Xu e180dec331
go: Unify react implementation 2019-10-14 18:01:36 +07:00
23 changed files with 512 additions and 143 deletions

@ -0,0 +1,40 @@
package db
func GetActiveLevel(discordID string) (int, error) {
rows, err := db.Query("SELECT level FROM activeLevel WHERE id=?", discordID)
if err != nil {
return 0, err
}
defer rows.Close()
if rows.Next() {
var activeLevel int
err := rows.Scan(&activeLevel)
if err != nil {
return 0, err
}
return activeLevel, nil
}
return 0, ErrNotFound
}
func GetTotalActiveLevel() (int, error) {
rows, err := db.Query("SELECT sum(level) FROM activeLevel")
if err != nil {
return 0, err
}
defer rows.Close()
if rows.Next() {
var totalActiveLevel int
err := rows.Scan(&totalActiveLevel)
if err != nil {
return 0, err
}
return totalActiveLevel, nil
}
return 0, ErrNotFound
}
func AddActiveLevel(discordID string, addedLevel int) error {
_, err := db.Exec("INSERT INTO activeLevel(id, level) VALUES(?, ?) ON CONFLICT(id) DO UPDATE SET level=level+?", discordID, addedLevel, addedLevel)
return err
}

@ -39,4 +39,8 @@ func init() {
if err != nil {
panic(err)
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS activeLevel (id TEXT, level INTEGER, UNIQUE(id))")
if err != nil {
panic(err)
}
}

@ -104,7 +104,7 @@ func GetVoteType(voteID int) (string, error) {
func UpdateVote(voteID int, userID string, voteValue int) error {
if voteValue == ForceRejectionVote {
//Check if they used it within a month.
rows, err := db.Query("SELECT voteId FROM choices WHERE date >= ? AND value=?", time.Now().AddDate(0, -1, 0))
rows, err := db.Query("SELECT voteId FROM choices WHERE date >= ? AND value=?", time.Now().AddDate(0, -1, 0), ForceRejectionVote)
if err != nil {
return err
}

@ -6,30 +6,35 @@ import (
"github.com/bwmarrin/discordgo"
"TerraOceanBot/db"
"TerraOceanBot/discord/config"
)
var ErrMemberNotFound = errors.New("discord/backend: the requested member was not found")
func GetAllMembers(s *discordgo.Session) ([]*discordgo.Member, error) {
initial, err := s.GuildMembers(config.GuildID, "", 1000)
err := s.RequestGuildMembers(config.GuildID, "", 0)
if err != nil {
return nil, err
}
next := initial[len(initial)-1].User.ID
for next != "" {
nextArray, err := s.GuildMembers(config.GuildID, next, 1000)
guild, err := s.State.Guild(config.GuildID)
if err != nil {
guild, err = s.Guild(config.GuildID)
}
if err != nil {
return nil, err
}
if len(nextArray) == 0 {
next = ""
} else {
next = nextArray[len(nextArray)-1].User.ID
initial = append(initial, nextArray...)
//TODO Deduplication hack until discordgo fixes this.
deduplicationCache := make(map[string]bool)
dedupedArray := make([]*discordgo.Member, 0)
for _, v := range guild.Members {
if deduplicationCache[v.User.ID] {
continue
}
dedupedArray = append(dedupedArray, v)
deduplicationCache[v.User.ID] = true
}
return initial, nil
return dedupedArray, nil
}
func GetMemberFromUserFriendlyName(s *discordgo.Session, user string) (*discordgo.Member, error) {
@ -71,5 +76,17 @@ func GetMemberFromUserFriendlyName(s *discordgo.Session, user string) (*discordg
return v, nil
}
}
for _, v := range allMembers {
mcUsername, err := db.GetMinecraftUsername(v.User.ID)
if err == db.ErrNotFound {
continue
}
if err != nil {
return nil, err
}
if mcUsername == user {
return v, nil
}
}
return nil, ErrMemberNotFound
}

@ -9,10 +9,9 @@ import (
"TerraOceanBot/server"
)
func UpdateVoiceChannelState(s *discordgo.Session) {
for _, guild := range s.State.Guilds {
func UpdateVoiceChannelState(s *discordgo.Session, guild *discordgo.GuildCreate) {
if guild.ID != config.GuildID {
continue
return
}
for _, vs := range guild.VoiceStates {
if vs.ChannelID != config.VoiceChannel {
@ -26,13 +25,11 @@ func UpdateVoiceChannelState(s *discordgo.Session) {
continue
}
if err != nil {
message.AuditError(s, "", err)
continue
}
server.ConnectUser(username)
}
}
}
func VoiceStateUpdate(s *discordgo.Session, vs *discordgo.VoiceState) {
username, err := db.GetMinecraftUsername(vs.UserID)

@ -9,11 +9,35 @@ import (
"TerraOceanBot/discord/config"
)
var trustCache map[string]float64
var trustCache = make(map[string]float64)
var ErrNotMember = errors.New("trust: only members have trust value")
func GetTrust(s *discordgo.Session, discordID string) (float64, error) {
rawScore, err := GetRawTrust(s, discordID)
if err != nil {
return 0.0, err
}
//Calculate active level
totalLevel, err := db.GetTotalActiveLevel()
if err != nil {
return rawScore, nil
}
if totalLevel < 1000000 {
return rawScore, nil
}
activeLevel, err := db.GetActiveLevel(discordID)
if err != nil && err != db.ErrNotFound {
//ErrNotFound is allowed. It just means the player hasn't played yet.
return 0.0, err
}
activeBuff := float64(activeLevel) / float64(totalLevel)
return rawScore * (1 + activeBuff), nil
}
func GetRawTrust(s *discordgo.Session, discordID string) (float64, error) {
if v, ok := trustCache[discordID]; ok {
return v, nil
}
@ -34,12 +58,17 @@ func GetTrust(s *discordgo.Session, discordID string) (float64, error) {
if err != nil {
return 0.0, err
}
votes = append(votes, 3)
if len(votes) == 0 {
return 0.0, nil
}
var total float64
for _, v := range votes {
total += float64(v)
}
return total / float64(len(votes)), nil
rawScore := total / float64(len(votes))
trustCache[discordID] = rawScore
return rawScore, nil
}
func GetTotalTrust(s *discordgo.Session) (float64, error) {

@ -1,5 +1,10 @@
package config
//==Help Command==
const HelpTitle = "指令列表 Command List"
const HelpColour = 0x00E58D
const HelpDescription = "这个信息包含了所有指令的资料。\nThis message contains all the information about available commands."
//==Message Util==
const UsageTitle = "使用方法 Command Usage"
const UsageColour = 0xF00000
@ -20,7 +25,7 @@ const InternalErrorDescription = "发生了不明错误。An unknown error has o
//==Vote==
const VoteErrorTitle = "投票错误 Vote Error"
const ErrorForceRejectionReuse = "你在这个月内已使用过:x:。请选择其他选项。\nYou have already used :x: this month. Please choose another option."
const ErrorVoteHasEnded = "无法投票给已经结束的投票。\nThis vote has already concluded."
const ErrorVoteHasEnded = "投票已结束。无法投票。\nThis vote has already concluded."
const VoteSuccessfulTitle = "投票成功 Voted Successfully"
const VoteSuccessfulVoteID = "投票编号 Vote ID"
const VoteSuccessfulVoteName = "投票名称 Vote Name"
@ -41,6 +46,7 @@ const EntryVoteColour = 0x808000
const EntryVoteVoteID = "投票编号 Vote ID"
const EntryVoteApplicant = "申请者 Applicant"
const EntryVoteReason = "加入原因 Provided Reason for Entry"
const EntryVoteName = "<@%s>的加入 <@%s>'s Entry Application"
const VotePassTitle = "投票通过 Vote Passed"
const VotePassColour = 0x00F000
@ -48,7 +54,7 @@ const VoteRejectTitle = "投票不通过 Vote Rejected"
const VoteRejectColour = 0xF00000
const EntryVoteRejectNotificationTitle = "抱歉…… Sorry…"
const EntryVoteRejectNotificationContent = "你加入的提案最终被拒绝了。请提升你和伺服器中的成员的关系后再次申请加入。\nThe application entry you sent was rejected. You must strengthen your relationship with other server members before trying again."
const EntryVoteRejectNotificationContent = "你加入的提案被拒绝了。请提升你和伺服器中的成员的关系后再次申请加入。\nThe application entry you sent was rejected. You must strengthen your relationship with other server members before trying again."
const EntryVoteSuccessWelcomeTitle = "欢迎! Welcome!"
const EntryVoteSuccessWelcomeDescription = "欢迎进入伺服器,<@%s>。Welcome to the server, <@%s>!"
@ -61,6 +67,7 @@ const EditAsUsage = "!editas <频道ID> <信息ID> <信息>\n!editas <channel ID
const EditAsSuccessfulDescription = "成功编辑该信息。\nMessage edited successfully."
//VTL is view trust level
const VTLUsage = "!peektrust [目标]\n!peektrust [target]"
const VTLColour = 0xE0E000
const VTLSingleUserTitle = "<@%s>的信誉分 <@%s>'s Trust Score"
const VTLSingleUserValue = "分数 Trust Score"
@ -68,13 +75,16 @@ const VTLSingleUserServerTotal = "伺服器总分 Server Total"
const VTLSingleUserPercentage = "百分比 Percentage"
const VTLGlobalTitle = "伺服器信誉分报告 Server Trust Score Report"
const VTLGlobalFieldFormat = "%.1f/%.1f (%.2f%%)"
const ErrorVTLTitle = "生成信誉分报告错误 Server Trust Score Report Generation Error"
const ErrorVTLTargetNotFound = "无法寻找到改变目标。\nCannot find the target user."
const InviteUsage = "!invite <要制造的验证码>\n!invite <validation code to create>"
const InviteSuccessTitle = "制造验证码成功 Validation Code Creation Success"
const InviteSuccessNew = "这个验证码现在属于你了。请好好保管并把它交给你要邀请的人。\nThis validation code now belongs to you. Pass it on to a user you want to invite."
const InviteSuccessAlreadyOwn = "该验证码原本就已经属于你。请直接交给你要邀请的人吧。\nThe validation code already belongs to you. You can pass it on to a user you want to invite."
const ErrorInviteTitle = "制造验证码错误 Create Validation Code Error"
const ErrorInviteTitle = "制造验证码失败 Create Validation Code Error"
const ErrorInviteCodeContainsSpace = "所制造的验证码不得有任何空格。\nThe validation code to create cannot contain any spaces."
const ErrorInviteCodeTooLong = "所制造的验证码必须少于512个字符。\nThe validation code cannot be longer than 512 characters."
const ErrorInviteOwnedByOthers = "这个验证码已被其他会员注册。请使用别的验证码。\nThis validation code is already registered. Please try another one."
const ErrorInviteExistAndUsed = "这个验证码已被使用过。请使用别的验证码。\nThis validation code has already been used. Please try another one."
@ -88,10 +98,10 @@ const ValidateConfirmationRejectCreatorTitle = "已拒绝验证码使用 Validat
const ValidateConfirmationRejectCreatorDescription = "你已成功拒绝该验证码的使用。为了避免该验证码被对方重复使用,该验证码已被无效化。\nYou have denied the usage of the validation code. To prevent the user from reusing this code, the validation code has been revoked."
const ValidateConfirmationRejectRejecteeTitle = "验证码使用已被拒绝 Validation Code Usage Denied"
const ValidateConfirmationRejectRejecteeDescription = "验证码制造者已拒绝你对该验证码的使用。验证码也已经被无效化了。\nThe validation code creator has prevented you from using the code."
const ValidateConfirmationAcceptCreatorTitle = "已同意验证码用 Validation Code Usage Approved"
const ValidateConfirmationAcceptCreatorDescription = "你已成功同意验证码的使用,主伺服器已开始了加入投票。该验证码也在这次使用后无效化了。\nYou have successfully approved the usage of the validation code and started a vote in the main server. With the one-time usage property of the code, it has been revoked."
const ValidateConfirmationAcceptAccepteeTitle = "验证码使用已被同意 Validation Code Usage Approved"
const ValidateConfirmationAcceptAccepteeDescription = "你验证码的使用已被同意,主伺服器开始了对于你的加入的投票,请等候投票完毕。\nYour usage of validation code was approved and a vote about your entry request has been started in the main server. Please wait for this process to finish."
const ValidateConfirmationAcceptCreatorTitle = "已同意验证码使用 Validation Code Usage Approved"
const ValidateConfirmationAcceptCreatorDescription = "你已成功同意验证码的使用,主伺服器已开始了加入投票。该验证码将在这次使用后无效化。\nYou have successfully approved the usage of the validation code and started a vote in the main server. With the one-time usage property of the code, it has been revoked."
const ValidateConfirmationAcceptAccepteeTitle = "验证码使用已被同意 Validation Code Usage Approved"
const ValidateConfirmationAcceptAccepteeDescription = "你验证码的使用已被同意,主伺服器开始了对于你的加入的投票,请等候投票完毕。\nYour usage of validation code was approved and a vote about your entry request has been started in the main server. Please wait for this process to finish."
const ValidateSuccess = "已向验证码制造者发送了请求。\nA request was sent to use the validation code to the creator."
const ErrorValidateTitle = "验证错误 Validation Error"
const ErrorValidationCodeReuse = "该验证码已被使用过。请向验证码制造者要求新的验证码。\nThis validation code has already been used. Please request a new one from its creator."
@ -110,7 +120,7 @@ const SetMCUsernameConfirmNewUsername = "新用户名 New Username"
const SetMCUsernameConfirmDeniedTitle = "成功取消设置用户名 Cancelled Set MC Username"
const SetMCUsernameConfirmDeniedDescription = "你的用户名没有被改变。请继续使用旧用户名。\nYour username was not changed."
const SetMCUsernameErrorTitle = "设定MC用户名错误 Set MC Username Error"
const ErrorSetMCUsernameDuplicate = "你要设置的用户名已经是你目前记录的用户名。\nThe username you want to set is equal to a username in the record."
const ErrorSetMCUsernameDuplicate = "你要设置的用户名已经是你目前记录的用户名。\nThe username you want to set is equal to a username in the record."
const ErrorSetMCUsernameAlreadyOwned = "你要设置的用户名已被其他人使用了。\nThe username you requested is already being used by another player."
const ChangeTrustUsage = "!trust <更改对象>\n!trust <target>"
@ -119,14 +129,19 @@ const ChangeTrustPromptColour = 0xF0F000
const ChangeTrustPromptID = "Discord编号 Discord ID"
const ChangeTrustPromptNick = "昵称 Nickname"
const ChangeTrustPromptReminderTitle = "提醒 Reminder"
const ChangeTrustPromptReminderDescription = "你一个月只能更换对一个玩家的评分一次。\nYou can only change your trust towards a player once per month."
const ChangeTrustPromptReminderDescription = "你一个月只能更换对一个玩家的评分进行一次更改。\nYou can only change your trust towards a player once per month."
const ChangeTrustSuccessTitle = "成功更新玩家评分 Update Player Score Success"
const ChangeTrustSuccessDescription = "已改变你给这名玩家的分数已被记录。\nThe score has been updated."
const ChangeTrustSuccessDescription = "你对这名玩家的分数改变已被记录。\nThe score has been updated."
const ErrorChangeTrustTitle = "调整信誉分错误 Change Trust Error"
const ErrorChangeTrustTargetNotFound = "无法寻找到改变目标。\nCannot find the target user."
const ErrorChangeTrustRecentlyChanged = "你在一个月内有设定过该名玩家的分数。你这次的更动没有被记录。\nYou have already changed your score for this player within the previous month. Your change was not recorded."
const ErrorChangeTrustSelf = "无法改变自己的分数。\nCannot set your own score."
const ErrorChangeTrustNotMember = "目标用户不是伺服器成员。\nTarget user is not a server member."
const ErrorChangeTrustRecentlyChanged = "你在一个月内设定过该名玩家的分数。你这次的更动没有被记录。\nYou have already changed your score for this player within the previous month. Your change was not recorded."
const VoteSuggestionUsage = "!votesuggest <种类custom> <内容>\n!votesuggest <type: custom> <content>"
const VoteSuggestionUpcomingTitle = "准备中…… Preparing…"
const VoteSuggestionUpcomingDescription = "正在准备新的一个投票……\nPreparing for the next vote..."
const VoteSuggestionUpcomingDescription = "正在准备新的投票……\nPreparing for the next vote..."
const VoteSuggestionUpcomingColour = 0x000000
const BatchRoleUsage = "!batchrole <Discord编号>\n!batchrole <Discord ID>"
const BatchRoleSuccessMessage = "成功分发角色。 Distributed role successfully."

@ -17,18 +17,20 @@ func StartBot(token string, kill chan bool) {
}
dg.AddHandler(ProcessCommand)
dg.AddHandler(ProcessVoiceStateUpdate)
dg.AddHandler(modules.CheckForReact)
dg.AddHandler(modules.CheckForVote)
dg.AddHandler(modules.CheckForTrustUpdate)
dg.AddHandler(modules.CheckForInvite)
dg.AddHandler(modules.ConfirmMinecraftUsername)
dg.AddHandler(backend.UpdateVoiceChannelState)
dg.State.TrackMembers = true
dg.State.TrackVoice = true
if err := dg.Open(); err != nil {
panic(err)
}
fmt.Println("Bot is now running. Press CTRL-C to exit.")
modules.GenerateHelpEmbed()
go modules.ListenToVoteFinishes(dg)
go backend.UpdateVoiceChannelState(dg)
<-kill

@ -1,6 +1,9 @@
package modules
import (
"crypto/rand"
"crypto/sha512"
"encoding/hex"
"fmt"
"github.com/bwmarrin/discordgo"
@ -50,8 +53,17 @@ var editAs = adminOnly(enforceArgumentCount(config.EditAsUsage, 4,
))
var viewTrustLevel = adminOnly(func(s *discordgo.Session, m *discordgo.MessageCreate, command []string) {
if len(command) > 1 {
value, err := backend.GetTrust(s, command[1])
if len(command) > 1 && command[1] != "unobf" {
user, err := backend.GetMemberFromUserFriendlyName(s, command[1])
if err != nil {
message.InitNewEmbed(
config.ErrorVTLTitle,
config.ErrorVTLTargetNotFound,
config.ErrorColour,
).Send(s, m.ChannelID)
return
}
value, err := backend.GetTrust(s, user.User.ID)
if err != nil {
message.AuditError(s, m.ChannelID, err)
return
@ -81,7 +93,12 @@ var viewTrustLevel = adminOnly(func(s *discordgo.Session, m *discordgo.MessageCr
return
}
salt := make([]byte, 16)
rand.Read(salt)
//Generate message
hash := sha512.New()
hash.Write(salt)
msg := message.NewEmbed().SetColour(config.VTLColour).SetTitle(config.VTLGlobalTitle)
for _, v := range members {
value, err := backend.GetTrust(s, v.User.ID)
@ -92,10 +109,48 @@ var viewTrustLevel = adminOnly(func(s *discordgo.Session, m *discordgo.MessageCr
message.AuditError(s, m.ChannelID, err)
return
}
var username string
if len(command) > 1 {
username = v.User.Username + "#" + v.User.Discriminator
} else {
toSalt := []byte(v.User.Username + "#" + v.User.Discriminator)
username = hex.EncodeToString(hash.Sum(toSalt))[:64]
if username == "" {
username = "<cannot calculate hash>"
}
}
msg.AddInlineField(
v.User.Username+"#"+v.User.Discriminator,
username,
fmt.Sprintf(config.VTLGlobalFieldFormat, value, total, value/total*100),
)
}
msg.Send(s, m.ChannelID)
_, err = msg.Send(s, m.ChannelID)
if err != nil {
message.AuditError(s, m.ChannelID, err)
return
}
})
var batchGiveRole = adminOnly(enforceDM(enforceArgumentCount(
config.BatchRoleUsage, 2,
func(s *discordgo.Session, m *discordgo.MessageCreate, command []string) {
members, err := backend.GetAllMembers(s)
if err != nil {
message.AuditError(s, m.ChannelID, err)
return
}
for _, member := range members {
err := s.GuildMemberRoleAdd(
config.GuildID,
member.User.ID,
command[1],
)
if err != nil {
message.AuditError(s, m.ChannelID, err)
return
}
}
message.InitNewEmbed(config.SuccessTitle, config.BatchRoleSuccessMessage, config.SuccessColour).
Send(s, m.ChannelID)
},
)))

@ -1,44 +1,81 @@
package modules
import "github.com/bwmarrin/discordgo"
import (
"TerraOceanBot/discord/config"
"github.com/bwmarrin/discordgo"
)
type CommandHandler func(s *discordgo.Session, m *discordgo.MessageCreate, commands []string)
type Command struct {
Name string
Handler CommandHandler
Description string
Usage string
Admin bool
}
var Commands = []Command{
//Admin Commands
Command{
Name: "!sendas",
Handler: sendAs,
Name: "!batchrole",
Handler: batchGiveRole,
Usage: config.BatchRoleUsage,
Admin: true,
},
Command{
Name: "!editas",
Handler: editAs,
Usage: config.EditAsUsage,
Admin: true,
},
Command{
Name: "!peektrust",
Handler: viewTrustLevel,
Usage: config.VTLUsage,
Admin: true,
},
Command{
Name: "!sendas",
Handler: sendAs,
Usage: config.SendAsUsage,
Admin: true,
},
//Regular Commands
Command{
Name: "!help",
Handler: showHelp,
Usage: "!help",
Admin: false,
},
Command{
Name: "!invite",
Handler: createInvite,
Usage: config.InviteUsage,
Admin: false,
},
Command{
Name: "!validate",
Handler: checkUseInvite,
Name: "!setmcusername",
Handler: updateMinecraftUsername,
Usage: config.SetMCUsernameUsage,
Admin: false,
},
Command{
Name: "!trust",
Handler: changeTrust,
Usage: config.ChangeTrustUsage,
Admin: false,
},
Command{
Name: "!setmcusername",
Handler: updateMinecraftUsername,
Name: "!validate",
Handler: checkUseInvite,
Usage: config.ValidateUsage,
Admin: false,
},
Command{
Name: "!votesuggest",
Handler: voteSuggestion,
Usage: config.VoteSuggestionUsage,
Admin: false,
},
}

@ -0,0 +1,38 @@
package modules
import (
"TerraOceanBot/discord/config"
"TerraOceanBot/discord/message"
"github.com/bwmarrin/discordgo"
)
var embed *discordgo.MessageEmbed
//GenerateHelpEmbed sets up the embed variable above to be used to display the !help command.
func GenerateHelpEmbed() {
emb := message.NewEmbed().
SetTitle(config.HelpTitle).
SetColour(config.HelpColour).
SetDescription(config.HelpDescription)
for _, cmd := range Commands {
if cmd.Admin {
continue
}
usage := cmd.Usage
if usage == "" {
usage = "No usage provided"
}
emb.AddField(cmd.Name, usage)
}
embed = emb.Build()
}
func showHelp(s *discordgo.Session, m *discordgo.MessageCreate, commands []string) {
_, err := s.ChannelMessageSendEmbed(m.ChannelID, embed)
if err != nil {
message.AuditError(s, m.ChannelID, err)
}
}

@ -12,8 +12,6 @@ import (
"TerraOceanBot/server"
)
var pendingInviteConfirmation = make(map[string]invite)
type invite struct {
User string
Reason string
@ -27,6 +25,11 @@ var createInvite = enforceDM(memberFilter(true, enforceArgumentCount(
Send(s, m.ChannelID)
return
}
if len(command[1]) > 512 {
message.InitNewEmbed(config.ErrorInviteTitle, config.ErrorInviteCodeTooLong, config.ErrorColour).
Send(s, m.ChannelID)
return
}
err := db.SetInviteOwner(command[1], m.Author.ID)
switch err {
@ -62,7 +65,7 @@ var checkUseInvite = enforceDM(memberFilter(false, enforceArgumentCount(
}
if err == db.ErrNotFound {
message.InitNewEmbed(config.ErrorValidateTitle, config.ErrorValidationCodeNotExist, config.ErrorColour).
SendPM(s, m.ChannelID)
Send(s, m.ChannelID)
return
}
if err != nil {
@ -85,14 +88,14 @@ var checkUseInvite = enforceDM(memberFilter(false, enforceArgumentCount(
}
if !isMember {
message.InitNewEmbed(config.ErrorValidateTitle, config.ErrorValidationCodeNotOwnedByMember, config.ErrorColour).
SendPM(s, m.ChannelID)
Send(s, m.ChannelID)
return
}
channel, err := s.UserChannelCreate(inviter)
if err != nil {
message.InitNewEmbed(config.ErrorValidateTitle, config.ErrorValidationCodeCreatorDisabledPM, config.ErrorColour).
SendPM(s, m.ChannelID)
Send(s, m.ChannelID)
return
}
msg, err := message.InitNewEmbed(
@ -106,30 +109,25 @@ var checkUseInvite = enforceDM(memberFilter(false, enforceArgumentCount(
message.AuditErrorPM(s, m.ChannelID, err)
return
}
pendingInviteConfirmation[msg.ID] = invite{
queueForReact(
msg.ID,
[]string{emojiCheck, emojiX},
invite{
User: m.Author.ID,
Reason: command[2],
}
},
inviteCallback,
)
s.MessageReactionAdd(channel.ID, msg.ID, emojiCheck)
s.MessageReactionAdd(channel.ID, msg.ID, emojiX)
message.InitNewEmbed(config.SuccessTitle, config.ValidateSuccess, config.SuccessColour).
SendPM(s, m.ChannelID)
Send(s, m.ChannelID)
},
)))
func CheckForInvite(s *discordgo.Session, r *discordgo.MessageReactionAdd) {
if r.UserID == s.State.User.ID {
return
}
invite, ok := pendingInviteConfirmation[r.MessageID]
if !ok {
return
}
delete(pendingInviteConfirmation, r.MessageID)
switch r.Emoji.Name {
case emojiX:
func inviteCallback(s *discordgo.Session, r *discordgo.MessageReactionAdd, info interface{}) {
invite := info.(invite)
if r.Emoji.Name == emojiX {
message.InitNewEmbed(
config.ValidateConfirmationRejectCreatorTitle,
config.ValidateConfirmationRejectCreatorDescription,
@ -141,23 +139,24 @@ func CheckForInvite(s *discordgo.Session, r *discordgo.MessageReactionAdd) {
config.ErrorColour,
).SendPM(s, invite.User)
return
case emojiCheck:
default:
return
}
message.InitNewEmbed(
config.ValidateConfirmationAcceptCreatorTitle,
config.ValidateConfirmationAcceptCreatorDescription,
config.ErrorColour,
config.SuccessColour,
).SendPM(s, r.UserID)
message.InitNewEmbed(
config.ValidateConfirmationAcceptAccepteeTitle,
config.ValidateConfirmationAcceptAccepteeDescription,
config.ErrorColour,
config.SuccessColour,
).SendPM(s, invite.User)
msg, err := s.ChannelMessageSend(config.VoteChannel, "正在准备新的一个投票…… Preparing for the next vote...")
msg, err := message.InitNewEmbed(
config.VoteSuggestionUpcomingTitle,
config.VoteSuggestionUpcomingDescription,
config.VoteSuggestionUpcomingColour,
).Send(s, config.VoteChannel)
if err != nil {
message.AuditErrorPM(s, r.UserID, err)
return

@ -4,14 +4,11 @@ import (
"github.com/bwmarrin/discordgo"
"TerraOceanBot/db"
"TerraOceanBot/discord/backend"
"TerraOceanBot/discord/config"
"TerraOceanBot/discord/message"
"TerraOceanBot/server"
)
var minecraftConfirmReact = make(map[string]string)
var updateMinecraftUsername = memberFilter(true, enforceArgumentCount(
config.SetMCUsernameUsage, 2,
func(s *discordgo.Session, m *discordgo.MessageCreate, command []string) {
@ -39,23 +36,19 @@ var updateMinecraftUsername = memberFilter(true, enforceArgumentCount(
if err != nil {
message.AuditError(s, m.ChannelID, err)
}
minecraftConfirmReact[msg.ID] = command[1]
queueForReact(
msg.ID,
[]string{emojiCheck, emojiX},
command[1],
confirmMinecraftUsername,
)
s.MessageReactionAdd(msg.ChannelID, msg.ID, emojiCheck)
s.MessageReactionAdd(msg.ChannelID, msg.ID, emojiX)
},
))
func ConfirmMinecraftUsername(s *discordgo.Session, r *discordgo.MessageReactionAdd) {
if r.UserID == s.State.User.ID {
return
}
newUsername, ok := minecraftConfirmReact[r.MessageID]
if !ok {
return
}
delete(minecraftConfirmReact, r.MessageID)
func confirmMinecraftUsername(s *discordgo.Session, r *discordgo.MessageReactionAdd, info interface{}) {
newUsername := info.(string)
switch r.Emoji.Name {
case emojiCheck:
@ -83,9 +76,13 @@ func processUsernameUpdate(s *discordgo.Session, discordID, channelID, newUserna
return
}
backend.UpdateVoiceChannelState(s)
if oldErr == nil {
server.WhitelistRemove(oldUsername)
server.DisconnectUser(oldUsername)
}
if err == nil {
server.WhitelistAdd(newUsername)
server.ConnectUser(newUsername)
}
message.InitNewEmbed(

@ -0,0 +1,56 @@
package modules
import (
"sync"
"github.com/bwmarrin/discordgo"
)
var reactLock sync.Mutex
var reactCallbacks = make(map[string]reactEntry)
type reactCallback func(s *discordgo.Session, react *discordgo.MessageReactionAdd, info interface{})
type reactEntry struct {
acceptedEmoji []string
callback reactCallback
info interface{}
}
func queueForReact(messageID string, acceptedEmoji []string, info interface{}, callback reactCallback) {
reactLock.Lock()
defer reactLock.Unlock()
reactCallbacks[messageID] = reactEntry{
acceptedEmoji: acceptedEmoji,
callback: callback,
info: info,
}
}
func CheckForReact(s *discordgo.Session, r *discordgo.MessageReactionAdd) {
if r.UserID == s.State.User.ID {
return
}
entry, ok := reactCallbacks[r.MessageID]
if !ok {
return
}
reactLock.Lock()
foundEmoji := false
for _, v := range entry.acceptedEmoji {
if r.Emoji.Name == v {
foundEmoji = true
break
}
}
if !foundEmoji {
reactLock.Unlock()
return
}
delete(reactCallbacks, r.MessageID)
reactLock.Unlock()
entry.callback(s, r, entry.info)
}

@ -9,8 +9,6 @@ import (
"TerraOceanBot/discord/message"
)
var trustMessage = make(map[string]string)
var changeTrust = enforceDM(memberFilter(true, enforceArgumentCount(
config.ChangeTrustUsage, 2,
func(s *discordgo.Session, m *discordgo.MessageCreate, command []string) {
@ -28,6 +26,30 @@ var changeTrust = enforceDM(memberFilter(true, enforceArgumentCount(
return
}
if member.User.ID == m.Author.ID {
message.InitNewEmbed(
config.ErrorChangeTrustTitle,
config.ErrorChangeTrustSelf,
config.ErrorColour,
).Send(s, m.ChannelID)
return
}
isMember := false
for _, v := range member.Roles {
if v == config.MemberRoleID {
isMember = true
}
}
if !isMember {
message.InitNewEmbed(
config.ErrorChangeTrustTitle,
config.ErrorChangeTrustNotMember,
config.ErrorColour,
).Send(s, m.ChannelID)
return
}
embed := message.NewEmbed().
SetTitle(config.ChangeTrustPromptTitle).
SetColour(config.ChangeTrustPromptColour).
@ -44,7 +66,12 @@ var changeTrust = enforceDM(memberFilter(true, enforceArgumentCount(
return
}
trustMessage[msg.ID] = member.User.ID
queueForReact(
msg.ID,
[]string{emojiOne, emojiTwo, emojiThree, emojiFour, emojiFive, emojiCheck},
member.User.ID,
trustCallback,
)
s.MessageReactionAdd(m.ChannelID, msg.ID, emojiOne)
s.MessageReactionAdd(m.ChannelID, msg.ID, emojiTwo)
@ -55,17 +82,8 @@ var changeTrust = enforceDM(memberFilter(true, enforceArgumentCount(
},
)))
func CheckForTrustUpdate(s *discordgo.Session, r *discordgo.MessageReactionAdd) {
if r.UserID == s.State.User.ID {
return
}
target, ok := trustMessage[r.MessageID]
if !ok {
return
}
delete(trustMessage, r.MessageID)
func trustCallback(s *discordgo.Session, r *discordgo.MessageReactionAdd, info interface{}) {
target := info.(string)
var value int
switch r.Emoji.Name {

@ -7,18 +7,20 @@ import (
)
type voteType struct {
//TODO Add FormatName(name string) to clean up vote name
EmbedBuilder func(id int, name string) *message.Embed
FormatName func(internalName string) string
ResultHandler func(s *discordgo.Session, id int, name string, isPositive bool)
}
var voteTypes = map[string]voteType{
"custom": voteType{
EmbedBuilder: createCustomEmbed,
FormatName: formatCustomName,
ResultHandler: announceCustomResult,
},
"invite": voteType{
EmbedBuilder: createInviteEmbed,
FormatName: formatInviteName,
ResultHandler: handleInviteResult,
},
}

@ -15,6 +15,10 @@ func createCustomEmbed(id int, name string) *message.Embed {
AddField(config.CustomVoteContent, name)
}
func formatCustomName(internalName string) string {
return internalName
}
func announceCustomResult(s *discordgo.Session, id int, name string, isPositive bool) {
createCustomEmbed(id, name).
UpdateVoteStatus(isPositive).

@ -21,6 +21,11 @@ func createInviteEmbed(id int, name string) *message.Embed {
AddField(config.EntryVoteReason, list[1])
}
func formatInviteName(internalName string) string {
list := strings.SplitN(internalName, ":", 2)
return fmt.Sprintf(config.EntryVoteName, list[0], list[0])
}
func handleInviteResult(s *discordgo.Session, id int, name string, isPositive bool) {
list := strings.SplitN(name, ":", 2)
if !isPositive {

@ -71,10 +71,13 @@ func CheckForVote(s *discordgo.Session, r *discordgo.MessageReactionAdd) {
return
}
var voteName string
var voteName, voteType string
if err == nil {
voteName, err = db.GetVoteName(voteID)
}
if err == nil {
voteType, err = db.GetVoteType(voteID)
}
if err != nil {
message.AuditErrorPM(s, r.UserID, err)
@ -83,7 +86,7 @@ func CheckForVote(s *discordgo.Session, r *discordgo.MessageReactionAdd) {
message.InitNewEmbed(config.VoteSuccessfulTitle, "", config.SuccessColour).
AddField(config.VoteSuccessfulVoteID, strconv.Itoa(voteID)).
AddField(config.VoteSuccessfulVoteName, voteName).
AddField(config.VoteSuccessfulVoteName, voteTypes[voteType].FormatName(voteName)).
AddField(config.VoteSuccessfulDetectedVote, r.Emoji.Name).
SendPM(s, r.UserID)
message.AuditInfo(s, fmt.Sprintf("<@%s> has chosen %s for vote ID %d.", r.UserID, r.Emoji.Name, voteID))
@ -94,7 +97,7 @@ func CheckForVote(s *discordgo.Session, r *discordgo.MessageReactionAdd) {
var voteSuggestion = enforceDM(enforceArgumentCount(
config.VoteSuggestionUsage, 3,
func(s *discordgo.Session, m *discordgo.MessageCreate, command []string) {
if len(command[1]) > 500 {
if len(command[2]) > 500 {
message.InitNewEmbed(
config.VoteSuggestionErrorTitle,
config.ErrorVoteSuggestionTooLong,
@ -103,7 +106,7 @@ var voteSuggestion = enforceDM(enforceArgumentCount(
return
}
switch command[0] {
switch command[1] {
case "custom":
msg, err := message.InitNewEmbed(
config.VoteSuggestionUpcomingTitle,
@ -114,14 +117,14 @@ var voteSuggestion = enforceDM(enforceArgumentCount(
message.AuditErrorPM(s, m.Author.ID, err)
return
}
id, err := db.CreateCustomVote(msg.ID, command[1])
id, err := db.CreateCustomVote(msg.ID, command[2])
if err != nil {
message.AuditErrorPM(s, m.Author.ID, err)
return
}
message.AuditInfo(s, fmt.Sprintf("Vote ID %d has been created by <@%s>.", id, m.Author.ID))
s.ChannelMessageEdit(config.VoteChannel, msg.ID, "")
createCustomEmbed(id, command[1]).Edit(s, config.VoteChannel, msg.ID)
createCustomEmbed(id, command[2]).Edit(s, config.VoteChannel, msg.ID)
s.MessageReactionAdd(config.VoteChannel, msg.ID, emojiOne)
s.MessageReactionAdd(config.VoteChannel, msg.ID, emojiTwo)
s.MessageReactionAdd(config.VoteChannel, msg.ID, emojiThree)

@ -18,6 +18,8 @@ type confirmedResult struct {
IsPositive bool
}
const invalidVote = -1
var voteMutex sync.Mutex
var toAnnounceResultList []confirmedResult
@ -25,6 +27,9 @@ func ListenToVoteFinishes(s *discordgo.Session) {
for {
voteMutex.Lock()
for _, v := range toAnnounceResultList {
if v.VoteID == invalidVote {
continue
}
message.AuditInfo(s, fmt.Sprintf("Announcing the result of vote %d.", v.VoteID))
if err := db.FinishVote(v.VoteID); err != nil {
message.AuditError(s, "", err)
@ -72,13 +77,13 @@ func checkForVoteResult(s *discordgo.Session, id int) {
lowestPossible := (currentScore + remainingTrust) / totalGlobalTrust
highestPossible := (currentScore + remainingTrust*5) / totalGlobalTrust
voteMutex.Lock()
defer voteMutex.Unlock()
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
}
}
@ -87,15 +92,11 @@ func checkForVoteResult(s *discordgo.Session, id int) {
VoteID: id,
IsPositive: false,
})
voteMutex.Unlock()
}
if lowestPossible >= 3.5 {
} else 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
}
}
@ -104,6 +105,17 @@ func checkForVoteResult(s *discordgo.Session, id int) {
VoteID: id,
IsPositive: true,
})
voteMutex.Unlock()
} else {
//Nothing happened. Make sure it's not in the queue.
for k, v := range toAnnounceResultList {
if v.VoteID == id {
//It's already queued to be announced in the next announcement cycle.
toAnnounceResultList[k] = confirmedResult{
VoteID: invalidVote,
IsPositive: false,
}
return
}
}
}
}

@ -3,8 +3,10 @@ module TerraOceanBot
go 1.13
require (
github.com/bwmarrin/discordgo v0.19.0
github.com/bwmarrin/discordgo v0.20.1
github.com/gorilla/mux v1.7.3 // indirect
github.com/gorilla/websocket v1.4.1
github.com/mattn/go-sqlite3 v1.11.0
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect
golang.org/x/sys v0.0.0-20191018095205-727590c5006e // indirect
)

@ -1,5 +1,5 @@
github.com/bwmarrin/discordgo v0.19.0 h1:kMED/DB0NR1QhRcalb85w0Cu3Ep2OrGAqZH1R5awQiY=
github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q=
github.com/bwmarrin/discordgo v0.20.1 h1:Ihh3/mVoRwy3otmaoPDUioILBJq4fdWkpsi83oj2Lmk=
github.com/bwmarrin/discordgo v0.20.1/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
@ -8,5 +8,14 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191018095205-727590c5006e h1:ZtoklVMHQy6BFRHkbG6JzK+S6rX82//Yeok1vMlizfQ=
golang.org/x/sys v0.0.0-20191018095205-727590c5006e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

@ -2,9 +2,12 @@ package server
import (
"fmt"
"strconv"
"strings"
"github.com/gorilla/websocket"
"TerraOceanBot/db"
)
var clients = make(map[*websocket.Conn]bool)
@ -27,8 +30,32 @@ func handleIncomingMessage(who *websocket.Conn, msg string) {
return
}
if !connectedUsers[message[1]] {
fmt.Println(message[1], "tried to connect. Disconnecting!")
fmt.Println(connectedUsers)
who.WriteMessage(websocket.TextMessage, []byte("leave "+message[1]))
}
case "active":
arg := strings.Split(msg, " ")
if len(arg) < 3 {
return
}
arg[1] = strings.Join(arg[1:len(arg)-1], " ")
arg[2] = arg[len(arg)-1]
id, err := db.GetDiscordID(arg[1])
if err != nil {
fmt.Println(err)
return
}
value, err := strconv.Atoi(arg[2])
if err != nil {
fmt.Println(err)
return
}
err = db.AddActiveLevel(id, value)
if err != nil {
fmt.Println(err)
return
}
default:
fmt.Println("Unknown message:" + msg)
}
@ -46,6 +73,7 @@ func ConnectUser(mcUsername string) {
func DisconnectUser(mcUsername string) {
delete(connectedUsers, mcUsername)
broadcast("leave " + mcUsername)
}
func RequestSync() {