├── .gitignore ├── config.env.example ├── go.mod ├── docker-compose-dev.yml ├── docker-compose.yml ├── Dockerfile ├── main.go ├── README.md ├── utils ├── discord.go └── setting.go ├── go.sum ├── app ├── app.go └── handlers.go └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .idea 3 | *.env 4 | appdata -------------------------------------------------------------------------------- /config.env.example: -------------------------------------------------------------------------------- 1 | TOKEN= 2 | SETTINGS= 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dont-genshin-bot 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/bwmarrin/discordgo v0.22.0 7 | github.com/joho/godotenv v1.3.0 8 | ) 9 | -------------------------------------------------------------------------------- /docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | dont-genshin-bot: 5 | build: . 6 | env_file: 7 | - config.env 8 | volumes: 9 | - ./appdata:/home/app/appdata 10 | logging: 11 | options: 12 | max-size: "1G" 13 | max-file: "10" 14 | restart: always 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | dont-genshin-bot: 5 | image: gizmooao/dont-genshin-bot:latest 6 | env_file: 7 | - config.env 8 | volumes: 9 | - ./appdata:/home/app/appdata 10 | logging: 11 | options: 12 | max-size: "1G" 13 | max-file: "10" 14 | restart: always 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.15-alpine AS builder 2 | RUN mkdir -p /go/src/app 3 | WORKDIR /go/src/app 4 | COPY . /go/src/app/ 5 | RUN apk add --no-cache git 6 | 7 | ENV GO111MODULE="on" 8 | ENV CGO_ENABLED=0 9 | 10 | RUN go build -o="goapp" 11 | 12 | FROM alpine:latest 13 | RUN mkdir -p /home/app 14 | WORKDIR /home/app 15 | COPY --from=builder /go/src/app /home/app 16 | ENTRYPOINT /home/app/goapp 17 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "dont-genshin-bot/app" 8 | "github.com/joho/godotenv" 9 | ) 10 | 11 | const envFile = "./config.env" 12 | 13 | func init() { 14 | log.SetFlags(log.Llongfile | log.LstdFlags) 15 | if _, err := os.Stat(envFile); err == nil { 16 | _ = godotenv.Load(envFile) 17 | } 18 | } 19 | 20 | func main() { 21 | app.Start() 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dont Genshin Bot 2 | 3 | [![Go Report](https://goreportcard.com/badge/github.com/GizmoOAO/dont-genshin-bot?style=flat-square)](https://goreportcard.com/report/github.com/GizmoOAO/dont-genshin-bot) 4 | [![GitHub](https://img.shields.io/github/license/GizmoOAO/dont-genshin-bot)](./LICENSE) 5 | 6 | 幫助戒原神Discord機器人, 自動Ban掉正在玩原神的用戶. 7 | 8 | # License 9 | 10 | Released under the [MIT license](./LICENSE). 11 | -------------------------------------------------------------------------------- /utils/discord.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/bwmarrin/discordgo" 5 | ) 6 | 7 | func IsGenshin(game *discordgo.Game) bool { 8 | switch game.Name { 9 | case "原神": 10 | return true 11 | case "Genshin Impact": 12 | return true 13 | } 14 | return false 15 | } 16 | 17 | func IsOwner(s *discordgo.Session, guildID string, userID string) (bool, error) { 18 | g, err := s.Guild(guildID) 19 | if err != nil { 20 | return false, err 21 | } 22 | return g.OwnerID == userID, nil 23 | } 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bwmarrin/discordgo v0.22.0 h1:uBxY1HmlVCsW1IuaPjpCGT6A2DBwRn0nvOguQIxDdFM= 2 | github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= 3 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 4 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 5 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 6 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 7 | golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA= 8 | golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 9 | -------------------------------------------------------------------------------- /app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "dont-genshin-bot/utils" 11 | "github.com/bwmarrin/discordgo" 12 | ) 13 | 14 | func Start() { 15 | utils.LoadSettings() 16 | client, err := discordgo.New("Bot " + os.Getenv("TOKEN")) 17 | if err != nil { 18 | log.Fatalln(err) 19 | } 20 | client.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAll) 21 | 22 | if err = client.Open(); err != nil { 23 | log.Fatalln("Error opening connection,", err) 24 | } 25 | defer client.Close() 26 | 27 | client.AddHandler(SettingHandler) 28 | client.AddHandler(DontGenshinHandler) 29 | 30 | fmt.Println("Bot is now running. Press CTRL-C to exit.") 31 | sc := make(chan os.Signal, 1) 32 | signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) 33 | <-sc 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gizmo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /utils/setting.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | var ( 14 | settings sync.Map 15 | ) 16 | 17 | type BotSetting struct { 18 | GuildId string 19 | // MainChannelId 發送消息的文字頻道的ID, 未設定不發送消息 20 | MainChannelId string 21 | // SendMessage 游玩原神后對用戶發送的消息, 未設定不發送消息 22 | SendMessage string 23 | // DefaultUserRoleId 默認用戶的身分組ID, Ban之後會移除, 解除Ban后會重新賦予 24 | DefaultUserRoleId string 25 | // BanRoleId Ban掉用戶之後賦予的身分組的ID 26 | BanRoleId string 27 | } 28 | 29 | func LoadSettings() { 30 | p := os.Getenv("SETTINGS") 31 | if _, err := os.Stat(p); err != nil { 32 | if err = os.MkdirAll(p, 0666); err != nil { 33 | panic(err) 34 | } 35 | } 36 | err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error { 37 | if info.IsDir() { 38 | return nil 39 | } 40 | f, _ := filepath.Abs(filepath.Join(p, info.Name())) 41 | data, err := ioutil.ReadFile(f) 42 | if err != nil { 43 | return err 44 | } 45 | var setting BotSetting 46 | if err = json.Unmarshal(data, &setting); err != nil { 47 | return err 48 | } 49 | guildId := strings.ReplaceAll(info.Name(), ".json", "") 50 | settings.Store(guildId, setting) 51 | return nil 52 | }) 53 | if err != nil { 54 | panic(err) 55 | } 56 | } 57 | 58 | func saveSetting(guildId string, s BotSetting) { 59 | s.GuildId = guildId 60 | filename := filepath.Join(os.Getenv("SETTINGS"), guildId+".json") 61 | data, err := json.Marshal(s) 62 | if err != nil { 63 | log.Println(err) 64 | return 65 | } 66 | if err = ioutil.WriteFile(filename, data, 0666); err != nil { 67 | log.Println(err) 68 | } 69 | } 70 | 71 | func GetSetting(guildId string) (setting BotSetting, ok bool) { 72 | s, ok := settings.Load(guildId) 73 | if !ok { 74 | return 75 | } 76 | return s.(BotSetting), true 77 | } 78 | 79 | func SetSetting(guildId string, s BotSetting) { 80 | settings.Store(guildId, s) 81 | saveSetting(guildId, s) 82 | } 83 | 84 | func GuildIds() []string { 85 | var ids []string 86 | settings.Range(func(k, v interface{}) bool { 87 | ids = append(ids, k.(string)) 88 | return false 89 | }) 90 | return ids 91 | } 92 | -------------------------------------------------------------------------------- /app/handlers.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | "dont-genshin-bot/utils" 11 | "github.com/bwmarrin/discordgo" 12 | ) 13 | 14 | const CmdKey = "!dgb" 15 | 16 | var genshinPlayer sync.Map 17 | 18 | func SettingHandler(s *discordgo.Session, e *discordgo.MessageCreate) { 19 | // 不處理Bot的消息和DM 20 | if e.Author.Bot || 21 | e.GuildID == "" || 22 | e.ChannelID == "" { 23 | return 24 | } 25 | arr := strings.Split(e.Content, " ") 26 | if arr[0] != CmdKey { 27 | return 28 | } 29 | isOwner, err := utils.IsOwner(s, e.GuildID, e.Author.ID) 30 | if err != nil { 31 | log.Println(err) 32 | return 33 | } else if !isOwner { 34 | _, _ = s.ChannelMessageSend(e.ChannelID, "Unauthorized") 35 | return 36 | } 37 | if len(arr) == 1 { 38 | botHelp(s, e.ChannelID) 39 | return 40 | } 41 | r := arr[1:] 42 | if len(r) >= 1 { 43 | switch { 44 | case r[0] == "help" && len(r) == 1: 45 | botHelp(s, e.ChannelID) 46 | case r[0] == "setting" && len(r) == 1: 47 | st, ok := utils.GetSetting(e.GuildID) 48 | if !ok { 49 | _, _ = s.ChannelMessageSend(e.ChannelID, "Setting not found.") 50 | return 51 | } 52 | _, _ = s.ChannelMessageSendEmbed(e.ChannelID, &discordgo.MessageEmbed{ 53 | Author: &discordgo.MessageEmbedAuthor{}, 54 | Color: 0x00ff00, 55 | Title: "Now settings", 56 | Fields: []*discordgo.MessageEmbedField{ 57 | { 58 | Name: "mainChannel", 59 | Value: func() string { 60 | if st.MainChannelId != "" { 61 | return st.MainChannelId 62 | } 63 | return "Not set" 64 | }(), 65 | Inline: false, 66 | }, 67 | { 68 | Name: "sendMessage", 69 | Value: func() string { 70 | if st.SendMessage != "" { 71 | return st.SendMessage 72 | } 73 | return "Not set" 74 | }(), 75 | Inline: false, 76 | }, 77 | { 78 | Name: "defaultUserRole", 79 | Value: func() string { 80 | if st.DefaultUserRoleId != "" { 81 | return st.DefaultUserRoleId 82 | } 83 | return "Not set" 84 | }(), 85 | Inline: false, 86 | }, 87 | { 88 | Name: "banRole", 89 | Value: func() string { 90 | if st.BanRoleId != "" { 91 | return st.BanRoleId 92 | } 93 | return "Not set" 94 | }(), 95 | Inline: false, 96 | }, 97 | }, 98 | Timestamp: time.Now().Format(time.RFC3339), 99 | }) 100 | case r[0] == "setting" && len(r) == 3 && r[1] == "mainChannel": 101 | st, ok := utils.GetSetting(e.GuildID) 102 | if !ok { 103 | st = utils.BotSetting{} 104 | } 105 | cid := r[2] 106 | if cid == "now" { 107 | cid = e.ChannelID 108 | } 109 | st.MainChannelId = cid 110 | utils.SetSetting(e.GuildID, st) 111 | _, _ = s.ChannelMessageSend(e.ChannelID, "Successfully updated.") 112 | case r[0] == "setting" && len(r) == 3 && r[1] == "defaultUserRole": 113 | st, ok := utils.GetSetting(e.GuildID) 114 | if !ok { 115 | st = utils.BotSetting{} 116 | } 117 | rid := r[2] 118 | if rid == "null" { 119 | rid = "" 120 | } 121 | st.DefaultUserRoleId = rid 122 | utils.SetSetting(e.GuildID, st) 123 | _, _ = s.ChannelMessageSend(e.ChannelID, "Successfully updated.") 124 | case r[0] == "setting" && len(r) == 3 && r[1] == "banRole": 125 | st, ok := utils.GetSetting(e.GuildID) 126 | if !ok { 127 | st = utils.BotSetting{} 128 | } 129 | st.BanRoleId = r[2] 130 | utils.SetSetting(e.GuildID, st) 131 | _, _ = s.ChannelMessageSend(e.ChannelID, "Successfully updated.") 132 | case r[0] == "setting" && len(r) >= 3 && r[1] == "sendMessage": 133 | st, ok := utils.GetSetting(e.GuildID) 134 | if !ok { 135 | st = utils.BotSetting{} 136 | } 137 | var msg string 138 | if r[2] == "default" { 139 | msg = "Don't play Genshin!" 140 | } else { 141 | msg = strings.Join(r[2:], " ") 142 | } 143 | st.SendMessage = msg 144 | utils.SetSetting(e.GuildID, st) 145 | _, _ = s.ChannelMessageSend(e.ChannelID, "Successfully updated.") 146 | } 147 | } 148 | } 149 | 150 | func DontGenshinHandler(s *discordgo.Session, e *discordgo.PresenceUpdate) { 151 | ids := utils.GuildIds() 152 | _, hp := genshinPlayer.Load(e.User.ID) 153 | if playingGenshin(e.Activities) { 154 | for _, id := range ids { 155 | if e.GuildID != id { 156 | continue 157 | } 158 | _, err := s.GuildMember(id, e.User.ID) 159 | if err == nil { 160 | setting, ok := utils.GetSetting(id) 161 | if ok { 162 | if setting.SendMessage != "" && !hp { 163 | msg := fmt.Sprintf("<@%s> %s", e.User.ID, setting.SendMessage) 164 | if setting.MainChannelId != "" { 165 | _, _ = s.ChannelMessageSend(setting.MainChannelId, msg) 166 | } 167 | } 168 | if !hp { 169 | ban(s, e.User.ID, setting) 170 | } 171 | } 172 | } 173 | } 174 | } else if hp { 175 | for _, id := range ids { 176 | _, err := s.GuildMember(id, e.User.ID) 177 | if err == nil { 178 | setting, ok := utils.GetSetting(id) 179 | if ok { 180 | unBan(s, e.User.ID, setting) 181 | } 182 | } 183 | } 184 | } 185 | } 186 | 187 | // playingGenshin 是否正在游玩原神 188 | func playingGenshin(activities []*discordgo.Game) bool { 189 | for _, game := range activities { 190 | if game.Type == discordgo.GameTypeGame && utils.IsGenshin(game) { 191 | return true 192 | } 193 | } 194 | return false 195 | } 196 | 197 | func unBan(s *discordgo.Session, userId string, setting utils.BotSetting) { 198 | if setting.DefaultUserRoleId != "" { 199 | _ = s.GuildMemberRoleAdd(setting.GuildId, userId, setting.DefaultUserRoleId) 200 | } 201 | if setting.BanRoleId != "" { 202 | _ = s.GuildMemberRoleRemove(setting.GuildId, userId, setting.BanRoleId) 203 | } 204 | genshinPlayer.Delete(userId) 205 | } 206 | 207 | func ban(s *discordgo.Session, userId string, setting utils.BotSetting) { 208 | if setting.DefaultUserRoleId != "" { 209 | _ = s.GuildMemberRoleRemove(setting.GuildId, userId, setting.DefaultUserRoleId) 210 | } 211 | if setting.BanRoleId != "" { 212 | _ = s.GuildMemberRoleAdd(setting.GuildId, userId, setting.BanRoleId) 213 | } 214 | genshinPlayer.Store(userId, struct{}{}) 215 | } 216 | 217 | // botHelp 發送機器人幫助信息 218 | func botHelp(s *discordgo.Session, channelId string) { 219 | _, _ = s.ChannelMessageSendEmbed(channelId, &discordgo.MessageEmbed{ 220 | Author: &discordgo.MessageEmbedAuthor{}, 221 | Color: 0x00ff00, 222 | Title: "Don't Genshin Bot", 223 | Description: "幫助戒原神Discord機器人, 自動Ban掉正在玩原神的用戶", 224 | Fields: []*discordgo.MessageEmbedField{ 225 | { 226 | Name: "help", 227 | Value: "獲取機器人指令列表.", 228 | Inline: false, 229 | }, 230 | { 231 | Name: "setting", 232 | Value: "獲取當前伺服器設定.", 233 | Inline: false, 234 | }, 235 | { 236 | Name: "setting mainChannel ", 237 | Value: "設定發送消息的文字頻道的ID, 未設定不發送消息. \n設定為now則為當前文字頻道.", 238 | Inline: false, 239 | }, 240 | { 241 | Name: "setting sendMessage ", 242 | Value: "游玩原神后對用戶發送的消息, 未設定不發送消息. \n設定為default則為`Don't play Genshin!`.", 243 | Inline: false, 244 | }, 245 | { 246 | Name: "setting defaultUserRole ", 247 | Value: "設定默認用戶的身分組ID, Ban之後會移除, 解除Ban后會重新賦予. \n設定為null則為無默認身分組.", 248 | Inline: false, 249 | }, 250 | { 251 | Name: "setting banRole ", 252 | Value: "設定Ban掉用戶之後賦予的身分組的ID.", 253 | Inline: false, 254 | }, 255 | }, 256 | Timestamp: time.Now().Format(time.RFC3339), 257 | }) 258 | } 259 | --------------------------------------------------------------------------------