diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 12e027b..cba1faa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -61,4 +61,4 @@ jobs: run: kubectl apply -f $GITHUB_WORKSPACE/deployment.yml - name: Verify deployment - run: kubectl rollout status deployment/geeksbot + run: kubectl rollout status -n discord-bots deployment/geeksbot diff --git a/Dockerfile b/Dockerfile index 2838ae9..0f844fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN go mod download COPY . . RUN go install github.com/dustinpianalto/geeksbot/... +RUN go get -u github.com/go-bindata/go-bindata/... CMD [ "go", "run", "cmd/geeksbot/main.go"] @@ -16,5 +17,6 @@ from alpine WORKDIR /bin COPY --from=dev /go/bin/geeksbot ./geeksbot +COPY --from=dev /go/bin/go-bindata ./go-bindata CMD [ "geeksbot" ] diff --git a/channel.go b/channel.go new file mode 100644 index 0000000..68dacb9 --- /dev/null +++ b/channel.go @@ -0,0 +1,18 @@ +package geeksbot + +type Channel struct { + ID string + Guild Guild + Admin bool + Default bool + NewPatron bool +} + +type ChannelService interface { + Channel(id string) (Channel, error) + CreateChannel(c Channel) (Channel, error) + DeleteChannel(c Channel) error + GuildChannels(g Guild) ([]Channel, error) + UpdateChannel(c Channel) (Channel, error) + GetOrCreateChannel(id string, guild_id string) (Channel, error) +} diff --git a/cmd/geeksbot/main.go b/cmd/geeksbot/main.go index 651be68..98f540d 100644 --- a/cmd/geeksbot/main.go +++ b/cmd/geeksbot/main.go @@ -9,6 +9,8 @@ import ( "github.com/bwmarrin/discordgo" "github.com/dustinpianalto/disgoman" "github.com/dustinpianalto/geeksbot/internal/exts" + "github.com/dustinpianalto/geeksbot/pkg/database" + "github.com/dustinpianalto/geeksbot/pkg/services" ) func main() { @@ -25,12 +27,9 @@ func main() { Intents: discordgo.MakeIntent(discordgo.IntentsAll), } - //postgres.ConnectDatabase(os.Getenv("DATABASE_URL")) - //postgres.InitializeDatabase() - //utils.LoadTestData() - - //us := &postgres.UserService{DB: postgres.DB} - //gs := &postgres.GuildService{DB: postgres.DB} + database.ConnectDatabase(os.Getenv("DATABASE_URL")) + database.RunMigrations() + services.InitializeServices() owners := []string{ "351794468870946827", @@ -48,18 +47,9 @@ func main() { // Add Command Handlers exts.AddCommandHandlers(&manager) - //services.InitalizeServices(us, gs) - - //if _, ok := handler.Commands["help"]; !ok { - // handler.AddDefaultHelpCommand() - //} dg.AddHandler(manager.OnMessage) dg.AddHandler(manager.StatusManager.OnReady) - //dg.AddHandler(guild_management.OnMessageUpdate) - //dg.AddHandler(guild_management.OnMessageDelete) - //dg.AddHandler(user_management.OnGuildMemberAddLogging) - //dg.AddHandler(user_management.OnGuildMemberRemoveLogging) err = dg.Open() if err != nil { @@ -70,9 +60,6 @@ func main() { // Start the Error handler in a goroutine go ErrorHandler(manager.ErrorChannel) - // Start the Logging handler in a goroutine - //go logging.LoggingHandler(logging.LoggingChannel) - log.Println("The Bot is now running.") sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) @@ -86,7 +73,11 @@ func main() { } func getPrefixes(guildID string) []string { - return []string{"G.", "g."} + guild, err := services.GuildService.Guild(guildID) + if err != nil || len(guild.Prefixes) == 0 { + return []string{"G$", "g$"} + } + return guild.Prefixes } func ErrorHandler(ErrorChan chan disgoman.CommandError) { diff --git a/go.mod b/go.mod index 6b9bef5..76fe8ce 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,10 @@ go 1.14 require ( github.com/bwmarrin/discordgo v0.22.1 github.com/dustinpianalto/disgoman v0.0.15 + github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect + github.com/golang-migrate/migrate v3.5.4+incompatible // indirect + github.com/golang-migrate/migrate/v4 v4.14.1 + github.com/gorcon/rcon v1.3.1 + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/lib/pq v1.8.0 ) diff --git a/go.sum b/go.sum index a1d06cb..18b928f 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,591 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg= +cloud.google.com/go v0.64.0/go.mod h1:xfORb36jGvE+6EexW71nMEtL025s3x6xvuYUKM4JLv4= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/spanner v1.9.0/go.mod h1:xvlEn0NZ5v1iJPYsBnUVRDNvccDxsBTEi16pJRKQVws= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/apache/arrow/go/arrow v0.0.0-20200601151325-b2287a20f230/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= +github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bwmarrin/discordgo v0.20.2/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= github.com/bwmarrin/discordgo v0.22.1 h1:254fNYyfqJWKbPzO5g8j/nUvRgj4dNlI19EB8rnkpt8= github.com/bwmarrin/discordgo v0.22.1/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= +github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustinpianalto/disgoman v0.0.15 h1:kdIw6jhC82WBut7+4BarqxBw06dozU+Hu47LQzkkoGM= github.com/dustinpianalto/disgoman v0.0.15/go.mod h1:v3FM6n+4dH9XlvO+IDx6MN3DUnGq6YVDBvy1A1k202g= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= +github.com/go-bindata/go-bindata v1.0.0 h1:DZ34txDXWn1DyWa+vQf7V9ANc2ILTtrEjtlsdJRF26M= +github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE= +github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang-migrate/migrate v1.3.2 h1:QAlFV1QF9zdkzy/jujlBVkVu+L/+k18cg8tuY1/4JDY= +github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= +github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk= +github.com/golang-migrate/migrate/v4 v4.14.1 h1:qmRd/rNGjM1r3Ve5gHd5ZplytrD02UcItYNxJ3iUHHE= +github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorcon/rcon v1.3.1 h1:z6a5iOlojfdkvA1qaKEng7QfCJuCzYlC9BUDs6/M+74= +github.com/gorcon/rcon v1.3.1/go.mod h1:2gztBPSV2WxkPkqV4jiJkdHs+NT46mNSGb8JxbPesx4= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= +github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= +github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/snowflakedb/glog v0.0.0-20180824191149-f5055e6f21ce/go.mod h1:EB/w24pR5VKI60ecFnKqXzxX3dOorz1rnVicQTQrGM0= +github.com/snowflakedb/gosnowflake v1.3.5/go.mod h1:13Ky+lxzIm3VqNDZJdyvu9MCGy+WgRdYFdXp96UcLZU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= +go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200911024640-645f7a48b24f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= +modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= +modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= +modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= +modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= +modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= +modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/guild.go b/guild.go new file mode 100644 index 0000000..4b86622 --- /dev/null +++ b/guild.go @@ -0,0 +1,29 @@ +package geeksbot + +import "database/sql" + +type Guild struct { + ID string + NewPatronMessage sql.NullString + Prefixes []string +} + +type Role struct { + ID string + RoleType string + Guild Guild +} + +type GuildService interface { + Guild(id string) (Guild, error) + CreateGuild(g Guild) (Guild, error) + DeleteGuild(g Guild) error + UpdateGuild(g Guild) (Guild, error) + GuildRoles(g Guild) ([]Role, error) + CreateRole(r Role) (Role, error) + Role(id string) (Role, error) + UpdateRole(r Role) (Role, error) + DeleteRole(r Role) error + GetOrCreateGuild(id string) (Guild, error) + CreateOrUpdateRole(r Role) (Role, error) +} diff --git a/internal/discord_utils/channel_utils.go b/internal/discord_utils/channel_utils.go new file mode 100644 index 0000000..356e7c0 --- /dev/null +++ b/internal/discord_utils/channel_utils.go @@ -0,0 +1,11 @@ +package discord_utils + +import "github.com/dustinpianalto/disgoman" + +func GetChannelName(ctx disgoman.Context, id string) string { + channel, err := ctx.Session.Channel(id) + if err != nil { + return "" + } + return channel.Name +} diff --git a/internal/discord_utils/checks.go b/internal/discord_utils/checks.go new file mode 100644 index 0000000..dc53b22 --- /dev/null +++ b/internal/discord_utils/checks.go @@ -0,0 +1,49 @@ +package discord_utils + +import ( + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/geeksbot" + "github.com/dustinpianalto/geeksbot/pkg/services" +) + +func IsGuildMod(ctx disgoman.Context, user geeksbot.User) bool { + discordCloser, err := ctx.Session.GuildMember(ctx.Guild.ID, user.ID) + if err != nil { + return false + } + guildRoles, err := services.GuildService.GuildRoles(geeksbot.Guild{ID: ctx.Guild.ID}) + if err != nil { + return false + } + for _, role := range guildRoles { + if role.RoleType == "moderator" { + for _, rID := range discordCloser.Roles { + if rID == role.ID { + return true + } + } + } + } + return false +} + +func IsGuildAdmin(ctx disgoman.Context, user geeksbot.User) bool { + discordCloser, err := ctx.Session.GuildMember(ctx.Guild.ID, user.ID) + if err != nil { + return false + } + guildRoles, err := services.GuildService.GuildRoles(geeksbot.Guild{ID: ctx.Guild.ID}) + if err != nil { + return false + } + for _, role := range guildRoles { + if role.RoleType == "admin" { + for _, rID := range discordCloser.Roles { + if rID == role.ID { + return true + } + } + } + } + return false +} diff --git a/internal/discord_utils/errors.go b/internal/discord_utils/errors.go new file mode 100644 index 0000000..3fc5bf5 --- /dev/null +++ b/internal/discord_utils/errors.go @@ -0,0 +1,11 @@ +package discord_utils + +import "github.com/dustinpianalto/disgoman" + +func SendErrorMessage(ctx disgoman.Context, msg string, err error) { + ctx.CommandManager.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: msg, + Error: err, + } +} diff --git a/internal/discord_utils/user_utils.go b/internal/discord_utils/user_utils.go new file mode 100644 index 0000000..cbeae27 --- /dev/null +++ b/internal/discord_utils/user_utils.go @@ -0,0 +1,14 @@ +package discord_utils + +import "github.com/dustinpianalto/disgoman" + +func GetDisplayName(ctx disgoman.Context, id string) string { + member, err := ctx.Session.GuildMember(ctx.Guild.ID, id) + if err != nil { + return "" + } + if member.Nick != "" { + return member.Nick + } + return member.User.Username +} diff --git a/internal/exts/arcon/arcon.go b/internal/exts/arcon/arcon.go new file mode 100644 index 0000000..86fe8c4 --- /dev/null +++ b/internal/exts/arcon/arcon.go @@ -0,0 +1,184 @@ +package arcon + +import ( + "fmt" + "log" + "strings" + + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/geeksbot" + "github.com/dustinpianalto/geeksbot/internal/discord_utils" + "github.com/dustinpianalto/geeksbot/pkg/services" + "github.com/gorcon/rcon" +) + +var ListplayersCommand = &disgoman.Command{ + Name: "listplayers", + Aliases: nil, + Description: "List the players currently connected to a ARK server.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: listplayersCommandFunc, +} + +func listplayersCommandFunc(ctx disgoman.Context, args []string) { + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error getting Guild from the database", err) + return + } + author, err := services.UserService.GetOrCreateUser(ctx.Message.Author.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Sorry, there was a problem getting your user.", err) + return + } + if !discord_utils.IsGuildAdmin(ctx, author) && !discord_utils.IsGuildMod(ctx, author) { + return + } + if len(args) == 0 { + servers, err := services.ServerService.GuildServers(guild) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Could not find any servers for this guild", err) + return + } + for _, server := range servers { + go listplayers(ctx, server) + } + return + } + serverName := strings.Join(args, " ") + server, err := services.ServerService.ServerByName(serverName, guild) + if err != nil { + discord_utils.SendErrorMessage(ctx, + fmt.Sprintf("Could not find **%s** in this guild.", serverName), + err, + ) + return + } + listplayers(ctx, server) +} + +func listplayers(ctx disgoman.Context, server geeksbot.Server) { + msg, err := ctx.Send(fmt.Sprintf("**Getting data for %s**", server.Name)) + if err != nil { + discord_utils.SendErrorMessage(ctx, "There was an error getting the player list", err) + return + } + conn, err := rcon.Dial(fmt.Sprintf("%s:%d", server.IPAddr, server.Port), server.Password) + if err != nil { + _, _ = ctx.Session.ChannelMessageEdit(ctx.Channel.ID, msg.ID, + fmt.Sprintf("**Could not open connection to %s**", server.Name), + ) + return + } + defer conn.Close() + response, err := conn.Execute("listplayers") + if err != nil { + _, _ = ctx.Session.ChannelMessageEdit(ctx.Channel.ID, msg.ID, + fmt.Sprintf("**There was a problem getting a response from %s**", server.Name), + ) + return + } + if strings.HasPrefix(response, "No Players") { + _, _ = ctx.Session.ChannelMessageEdit(ctx.Channel.ID, msg.ID, + fmt.Sprintf("**%s: %s**", server.Name, response), + ) + return + } + players := strings.Split(response, "\n") + for i, player := range players { + parts := strings.Split(player, ", ") + steamID := parts[len(parts)-1] + user, err := services.UserService.GetBySteamID(steamID) + if err == nil { + duser, err := ctx.Session.GuildMember(ctx.Guild.ID, user.ID) + if err == nil { + players[i] = fmt.Sprintf("%s (%s)", player, duser.Mention()) + } + } + } + _, _ = ctx.Session.ChannelMessageEdit(ctx.Channel.ID, msg.ID, + fmt.Sprintf("**%s:**%s", server.Name, strings.Join(players, "\n")), + ) +} + +var BroadcastCommand = &disgoman.Command{ + Name: "broadcast", + Aliases: nil, + Description: "Broadcast a message to ARK servers.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: broadcastCommandFunc, +} + +func broadcastCommandFunc(ctx disgoman.Context, args []string) { + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error getting Guild from the database", err) + return + } + author, err := services.UserService.GetOrCreateUser(ctx.Message.Author.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Sorry, there was a problem getting your user.", err) + return + } + if !discord_utils.IsGuildAdmin(ctx, author) && !discord_utils.IsGuildMod(ctx, author) { + return + } + message := strings.Join(args[1:len(args)], " ") + if strings.ToLower(args[0]) == "all" { + servers, err := services.ServerService.GuildServers(guild) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Could not find any servers for this guild", err) + return + } + for _, server := range servers { + go broadcast(ctx, server, message) + } + return + } else { + serverName := strings.Title(strings.ReplaceAll(args[0], "_", " ")) + server, err := services.ServerService.ServerByName(serverName, guild) + if err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("Could not find %s", serverName), err) + return + } + broadcast(ctx, server, message) + } +} + +func broadcast(ctx disgoman.Context, server geeksbot.Server, message string) { + msg, err := ctx.Send(fmt.Sprintf("**Broadcasting to: %s**", server.Name)) + if err != nil { + discord_utils.SendErrorMessage(ctx, "There was an error getting the player list", err) + return + } + conn, err := rcon.Dial(fmt.Sprintf("%s:%d", server.IPAddr, server.Port), server.Password) + if err != nil { + _, _ = ctx.Session.ChannelMessageEdit(ctx.Channel.ID, msg.ID, + fmt.Sprintf("**Could not open connection to %s**", server.Name), + ) + return + } + defer conn.Close() + userName := discord_utils.GetDisplayName(ctx, ctx.Message.Author.ID) + response, err := conn.Execute(fmt.Sprintf("broadcast %s: %s", userName, message)) + if err != nil { + _, _ = ctx.Session.ChannelMessageEdit(ctx.Channel.ID, msg.ID, + fmt.Sprintf("**There was a problem getting a response from %s**", server.Name), + ) + return + } + log.Printf("%T - %#v", response, response) + if strings.Contains(response, "Server received, But no response!!") { + _, _ = ctx.Session.ChannelMessageEdit(ctx.Channel.ID, msg.ID, + fmt.Sprintf("**%s Broadcast Successful**", server.Name), + ) + return + } + _, _ = ctx.Session.ChannelMessageEdit(ctx.Channel.ID, msg.ID, + fmt.Sprintf("**Broadcasting to %s Failed!**", server.Name), + ) +} diff --git a/internal/exts/guild/guild.go b/internal/exts/guild/guild.go new file mode 100644 index 0000000..81ac0a3 --- /dev/null +++ b/internal/exts/guild/guild.go @@ -0,0 +1,109 @@ +package guild + +import ( + "fmt" + + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/geeksbot" + "github.com/dustinpianalto/geeksbot/internal/discord_utils" + "github.com/dustinpianalto/geeksbot/internal/utils" + "github.com/dustinpianalto/geeksbot/pkg/services" +) + +var AddPrefixCommand = &disgoman.Command{ + Name: "addPrefix", + Aliases: []string{"ap"}, + Description: "Add a prefix for use in this guild.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: addPrefixCommandFunc, +} + +func addPrefixCommandFunc(ctx disgoman.Context, args []string) { + if len(args) == 0 { + discord_utils.SendErrorMessage(ctx, "Please include at least one prefix to add", nil) + return + } + guild, err := services.GuildService.Guild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Guild not configured, adding new guild to the database", nil) + guild = geeksbot.Guild{ + ID: ctx.Guild.ID, + Prefixes: args, + } + guild, err = services.GuildService.CreateGuild(guild) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error adding guild to database.", err) + return + } + } else { + guild.Prefixes = append(guild.Prefixes, args...) + guild.Prefixes = utils.RemoveDuplicateStrings(guild.Prefixes) + guild, err = services.GuildService.UpdateGuild(guild) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error adding prefixes to guild.", err) + return + } + } + _, err = ctx.Send(fmt.Sprintf("Prefixes Updates.\nThe Prefixes for this guild are currently %#v", guild.Prefixes)) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error sending update message", err) + } +} + +var RemovePrefixCommand = &disgoman.Command{ + Name: "removePrefix", + Aliases: []string{"rp"}, + Description: "Remove a prefix so it can't be used in this guild", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: removePrefixCommandFunc, +} + +func removePrefixCommandFunc(ctx disgoman.Context, args []string) { + if len(args) == 0 { + discord_utils.SendErrorMessage(ctx, "Please include at least one prefix to remove", nil) + return + } + guild, err := services.GuildService.Guild(ctx.Guild.ID) + var removed []string + if err != nil { + discord_utils.SendErrorMessage(ctx, "Guild not configured, adding new guild to the database", nil) + guild = geeksbot.Guild{ + ID: ctx.Guild.ID, + Prefixes: []string{}, + } + guild, err = services.GuildService.CreateGuild(guild) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error adding guild to database.", err) + return + } + } else { + for _, a := range args { + l := len(guild.Prefixes) + for i := 0; i < l; i++ { + if a == guild.Prefixes[i] { + guild.Prefixes = append(guild.Prefixes[:i], guild.Prefixes[i+1:]...) + l-- + i-- + removed = append(removed, a) + } + } + } + removed = utils.RemoveDuplicateStrings(removed) + guild.Prefixes = utils.RemoveDuplicateStrings(guild.Prefixes) + guild, err = services.GuildService.UpdateGuild(guild) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error removing prefixes from guild.", err) + return + } + } + _, err = ctx.Send(fmt.Sprintf("Prefixes Updates.\n"+ + "The Prefixes for this guild are currently %#v\n"+ + "Removed: %#v", guild.Prefixes, removed)) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error sending update message", err) + } +} diff --git a/internal/exts/guild/roles.go b/internal/exts/guild/roles.go new file mode 100644 index 0000000..2c7904e --- /dev/null +++ b/internal/exts/guild/roles.go @@ -0,0 +1,404 @@ +package guild + +import ( + "fmt" + "strconv" + "strings" + + "github.com/bwmarrin/discordgo" + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/geeksbot" + "github.com/dustinpianalto/geeksbot/internal/discord_utils" + "github.com/dustinpianalto/geeksbot/internal/utils" + "github.com/dustinpianalto/geeksbot/pkg/services" +) + +var AddModeratorRoleCommand = &disgoman.Command{ + Name: "addMod", + Aliases: []string{"addModerator", "addModRole"}, + Description: "Add a role which is allowed to run moderator commands", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: addModeratorRoleCommandFunc, +} + +func addModeratorRoleCommandFunc(ctx disgoman.Context, args []string) { + var count int + added := make(map[string]bool) + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Something went wrong getting the guild", err) + return + } + roles := append(args, ctx.Message.MentionRoles...) + if len(roles) > 0 { + for _, id := range roles { + if strings.HasPrefix(id, "<@&") && strings.HasSuffix(id, ">") { + continue + } + if _, ok := added[id]; ok { + continue + } + if _, err = ctx.Session.State.Role(ctx.Guild.ID, id); err != nil { + _, _ = ctx.Send(fmt.Sprintf("%s does not reference a valid role for this guild.", id)) + continue + } + _, err := services.GuildService.CreateOrUpdateRole(geeksbot.Role{ + ID: id, + RoleType: "moderator", + Guild: guild, + }) + if err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("There was a problem adding <@&%s>", id), err) + continue + } + added[id] = true + count++ + _, _ = ctx.Send(fmt.Sprintf("Added <@&%s> as a moderator role.", id)) + } + _, _ = ctx.Send(fmt.Sprintf("Added %d moderator %s.", count, utils.PluralizeString("role", count))) + } else { + _, _ = ctx.Send("Please include at least one role to make a moderator role.") + } +} + +var AddAdminRoleCommand = &disgoman.Command{ + Name: "addAdmin", + Aliases: []string{"addAdminRole"}, + Description: "Add a role which is allowed to run admin commands", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: addAdminRoleCommandFunc, +} + +func addAdminRoleCommandFunc(ctx disgoman.Context, args []string) { + var count int + added := make(map[string]bool) + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Something went wrong getting the guild", err) + return + } + roles := append(args, ctx.Message.MentionRoles...) + if len(roles) > 0 { + for _, id := range roles { + if strings.HasPrefix(id, "<@&") && strings.HasSuffix(id, ">") { + continue + } + if _, ok := added[id]; ok { + continue + } + if _, err = ctx.Session.State.Role(ctx.Guild.ID, id); err != nil { + _, _ = ctx.Send(fmt.Sprintf("%s does not reference a valid role for this guild.", id)) + continue + } + _, err := services.GuildService.CreateOrUpdateRole(geeksbot.Role{ + ID: id, + RoleType: "admin", + Guild: guild, + }) + if err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("There was a problem adding <@&%s>", id), err) + continue + } + added[id] = true + count++ + _, _ = ctx.Send(fmt.Sprintf("Added <@&%s> as an admin role.", id)) + } + _, _ = ctx.Send(fmt.Sprintf("Added %d admin %s.", count, utils.PluralizeString("role", count))) + } else { + _, _ = ctx.Send("Please include at least one role to make an admin role.") + } +} + +var RemoveModRoleCommand = &disgoman.Command{ + Name: "removeMod", + Aliases: []string{"removeModeratorRole", "removeModRole", "removeAdmin", "removeAdminRole"}, + Description: "Remove a role or several roles from the moderator or admin list", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: removeModRoleCommandFunc, +} + +func removeModRoleCommandFunc(ctx disgoman.Context, args []string) { + var count int + added := make(map[string]bool) + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Something went wrong getting the guild", err) + return + } + roles := append(args, ctx.Message.MentionRoles...) + if len(roles) > 0 { + for _, id := range roles { + if strings.HasPrefix(id, "<@&") && strings.HasSuffix(id, ">") { + continue + } + if _, ok := added[id]; ok { + continue + } + if _, err = ctx.Session.State.Role(ctx.Guild.ID, id); err != nil { + if r, err := services.GuildService.Role(id); err != nil { + _, _ = ctx.Send(fmt.Sprintf("%s does not reference a valid role for this guild.", id)) + continue + } else { + err = services.GuildService.DeleteRole(r) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Something went wrong deleting the role", err) + } + _, _ = ctx.Send(fmt.Sprintf("Deleted <@&%s> as a no longer a valid role.", id)) + continue + } + } + _, err := services.GuildService.CreateOrUpdateRole(geeksbot.Role{ + ID: id, + RoleType: "normal", + Guild: guild, + }) + if err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("There was a problem updating <@&%s>", id), err) + continue + } + added[id] = true + count++ + _, _ = ctx.Send(fmt.Sprintf("Set <@&%s> as a normal role.", id)) + } + _, _ = ctx.Send(fmt.Sprintf("Set %d %s to normal.", count, utils.PluralizeString("role", count))) + } else { + _, _ = ctx.Send("Please include at least one role to remove from the moderator or admin lists.") + } +} + +var MakeRoleSelfAssignableCommand = &disgoman.Command{ + Name: "make-role-self-assignable", + Aliases: []string{"makesar", "addsar"}, + Description: "Makes the passed in role self assignable by anyone", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: makeRoleSelfAssignableCommandFunc, +} + +func makeRoleSelfAssignableCommandFunc(ctx disgoman.Context, args []string) { + added := make(map[string]bool) + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Something went wrong getting the guild", err) + return + } + + roles := append(args, ctx.Message.MentionRoles...) + if len(roles) > 0 { + for _, id := range roles { + if strings.HasPrefix(id, "<@&") && strings.HasSuffix(id, ">") { + continue + } + if _, ok := added[id]; ok { + continue + } + + var role *discordgo.Role + var err error + if role, err = ctx.Session.State.Role(ctx.Guild.ID, id); err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("%s does not reference a valid role for this guild", id), err) + return + } + + _, err = services.GuildService.CreateOrUpdateRole(geeksbot.Role{ + ID: role.ID, + RoleType: "sar", + Guild: guild, + }) + if err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("There was a problem updating <@&%s>", role.ID), err) + return + } + _, _ = ctx.Send(fmt.Sprintf("%s is now self assignable", role.Name)) + + } + } else { + _, _ = ctx.Send("Please include at least one role to make self assignable") + } + +} + +var RemoveSelfAssignableCommand = &disgoman.Command{ + Name: "remove-self-assignable-role", + Aliases: []string{"removesar"}, + Description: "Makes a role that was previously self assignable not so", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: removeSelfAssignableRoleCommandFunc, +} + +func removeSelfAssignableRoleCommandFunc(ctx disgoman.Context, args []string) { + removed := make(map[string]bool) + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Something went wrong getting the guild", err) + return + } + + roles := append(args, ctx.Message.MentionRoles...) + if len(roles) > 0 { + for _, id := range roles { + if strings.HasPrefix(id, "<@&") && strings.HasSuffix(id, ">") { + continue + } + if _, ok := removed[id]; ok { + continue + } + + var err error + var role *discordgo.Role + if role, err = ctx.Session.State.Role(ctx.Guild.ID, id); err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("%s does not reference a valid role for this guild", id), err) + return + } + _, err = services.GuildService.CreateOrUpdateRole(geeksbot.Role{ + ID: role.ID, + RoleType: "normal", + Guild: guild, + }) + if err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("There was a problem updating <@&%s>", role.ID), err) + return + } + _, _ = ctx.Send(fmt.Sprintf("%s's self assignability has been removed.", role.Name)) + } + } else { + _, _ = ctx.Send("Please include at least one role to make self assignable") + } + +} + +var SelfAssignRoleCommand = &disgoman.Command{ + Name: "giverole", + Aliases: []string{"iwant", "givetome", "addrole"}, + Description: "Assigns a person the passed in role if it is self assignable", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: selfAssignRoleCommandFunc, +} + +func selfAssignRoleCommandFunc(ctx disgoman.Context, args []string) { + added := make(map[string]bool) + roles := append(args, ctx.Message.MentionRoles...) + if len(roles) > 0 { + for _, id := range roles { + var roleID string + if strings.HasPrefix(id, "<@&") && strings.HasSuffix(id, ">") { + continue + } else if _, err := strconv.Atoi(id); err == nil { + roleID = id + } else { + for _, role := range ctx.Guild.Roles { + if strings.ToLower(id) == strings.ToLower(role.Name) { + roleID = role.ID + } + } + } + + if _, ok := added[id]; ok { + continue + } + var role *discordgo.Role + var err error + if role, err = ctx.Session.State.Role(ctx.Guild.ID, roleID); err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("%s does not reference a valid role for this guild", roleID), err) + return + } + if memberHasRole(ctx.Member, role.ID) { + _, _ = ctx.Send(fmt.Sprintf("You already have the %s role silly...", role.Name)) + return + } + r, err := services.GuildService.Role(role.ID) + if err != nil || r.RoleType != "sar" { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("You aren't allowed to assign yourself the %s role", role.Name), err) + return + } + err = ctx.Session.GuildMemberRoleAdd(ctx.Guild.ID, ctx.User.ID, role.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "There was a problem adding that role to you.", err) + return + } + _, _ = ctx.Send(fmt.Sprintf("Congratulations! The %s role has been added to your... Ummm... Thing.", role.Name)) + } + } else { + _, _ = ctx.Send("Please include at least one role to make self assignable") + } + +} + +var UnAssignRoleCommand = &disgoman.Command{ + Name: "removerole", + Aliases: []string{"idon'twant"}, + Description: "Removes a role from a person if the role is self assignable", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: unAssignRoleCommandFunc, +} + +func unAssignRoleCommandFunc(ctx disgoman.Context, args []string) { + removed := make(map[string]bool) + roles := append(args, ctx.Message.MentionRoles...) + if len(roles) > 0 { + for _, id := range roles { + var roleID string + if strings.HasPrefix(id, "<@&") && strings.HasSuffix(id, ">") { + continue + } else if _, err := strconv.Atoi(id); err == nil { + roleID = id + } else { + for _, role := range ctx.Guild.Roles { + if strings.ToLower(id) == strings.ToLower(role.Name) { + roleID = role.ID + } + } + } + if _, ok := removed[id]; ok { + continue + } + + var role *discordgo.Role + var err error + if role, err = ctx.Session.State.Role(ctx.Guild.ID, roleID); err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("%s does not reference a valid role for this guild", roleID), err) + return + } + if !memberHasRole(ctx.Member, role.ID) { + _, _ = ctx.Send(fmt.Sprintf("I can't remove the %s role from you because you don't have it...", role.Name)) + return + } + r, err := services.GuildService.Role(role.ID) + if err != nil || r.RoleType != "sar" { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("You aren't allowed to assign yourself the %s role", role.Name), err) + return + } + err = ctx.Session.GuildMemberRoleRemove(ctx.Guild.ID, ctx.User.ID, role.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "There was a problem removing that role from your account", err) + return + } + _, _ = ctx.Send(fmt.Sprintf("Sad to see you go... but the %s role has been removed.", role.Name)) + } + } else { + _, _ = ctx.Send("Please include at least one role to make self assignable") + } + +} + +func memberHasRole(m *discordgo.Member, id string) bool { + for _, r := range m.Roles { + if r == id { + return true + } + } + return false +} diff --git a/internal/exts/init.go b/internal/exts/init.go index 1817f5e..ef35532 100644 --- a/internal/exts/init.go +++ b/internal/exts/init.go @@ -2,10 +2,13 @@ package exts import ( "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/geeksbot/internal/exts/arcon" + "github.com/dustinpianalto/geeksbot/internal/exts/guild" + "github.com/dustinpianalto/geeksbot/internal/exts/requests" "github.com/dustinpianalto/geeksbot/internal/exts/utils" ) -func AddCommandHandlers(h *disgoman.CommandManager) { +func AddCommandHandlers(g *disgoman.CommandManager) { // Arguments: // name - command name - string // desc - command description - string @@ -14,9 +17,26 @@ func AddCommandHandlers(h *disgoman.CommandManager) { // perms - permissisions required - anpan.Permission (int) // type - command type, sets where the command is available // run - function to run - func(anpan.Context, []string) / CommandRunFunc - _ = h.AddCommand(utils.UserCommand) - _ = h.AddCommand(utils.SayCommand) - _ = h.AddCommand(utils.GitCommand) - _ = h.AddCommand(utils.InviteCommand) - _ = h.AddCommand(utils.PingCommand) + _ = g.AddCommand(utils.UserCommand) + _ = g.AddCommand(utils.AddUserCommand) + _ = g.AddCommand(utils.SayCommand) + _ = g.AddCommand(utils.GitCommand) + _ = g.AddCommand(utils.InviteCommand) + _ = g.AddCommand(utils.PingCommand) + _ = g.AddCommand(guild.AddPrefixCommand) + _ = g.AddCommand(guild.RemovePrefixCommand) + _ = g.AddCommand(guild.AddModeratorRoleCommand) + _ = g.AddCommand(guild.AddAdminRoleCommand) + _ = g.AddCommand(guild.RemoveModRoleCommand) + _ = g.AddCommand(guild.MakeRoleSelfAssignableCommand) + _ = g.AddCommand(guild.RemoveSelfAssignableCommand) + _ = g.AddCommand(guild.SelfAssignRoleCommand) + _ = g.AddCommand(guild.UnAssignRoleCommand) + _ = g.AddCommand(requests.RequestCommand) + _ = g.AddCommand(requests.CloseCommand) + _ = g.AddCommand(requests.ListCommand) + _ = g.AddCommand(requests.ViewCommand) + _ = g.AddCommand(requests.CommentCommand) + _ = g.AddCommand(arcon.ListplayersCommand) + _ = g.AddCommand(arcon.BroadcastCommand) } diff --git a/internal/exts/requests/requests.go b/internal/exts/requests/requests.go new file mode 100644 index 0000000..12bbfab --- /dev/null +++ b/internal/exts/requests/requests.go @@ -0,0 +1,449 @@ +package requests + +import ( + "fmt" + "log" + "strconv" + "strings" + "time" + + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/geeksbot" + "github.com/dustinpianalto/geeksbot/internal/discord_utils" + "github.com/dustinpianalto/geeksbot/internal/utils" + "github.com/dustinpianalto/geeksbot/pkg/services" +) + +var RequestCommand = &disgoman.Command{ + Name: "request", + Aliases: nil, + Description: "Submit a request for the guild staff", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: requestCommandFunc, +} + +func requestCommandFunc(ctx disgoman.Context, args []string) { + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error getting Guild from the database", err) + return + } + requestMsg := strings.Join(args, " ") + author, err := services.UserService.GetOrCreateUser(ctx.Message.Author.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error creating the request. Could not get user.", err) + return + } + channel, err := services.ChannelService.GetOrCreateChannel(ctx.Message.ChannelID, ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error creating the request. Could not get channel.", err) + return + } + int64ID, _ := strconv.ParseInt(ctx.Message.ID, 10, 64) + s := discord_utils.ParseSnowflake(int64ID) + message, err := services.MessageService.CreateMessage(geeksbot.Message{ + ID: ctx.Message.ID, + CreatedAt: s.CreationTime, + Content: ctx.Message.Content, + Channel: channel, + Author: author, + }) + request := geeksbot.Request{ + Author: author, + Channel: channel, + Guild: guild, + Content: requestMsg, + RequestedAt: s.CreationTime, + Completed: false, + Message: message, + } + request, err = services.RequestService.CreateRequest(request) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error creating the request", err) + return + } + channels, err := services.ChannelService.GuildChannels(guild) + if err == nil { + var mentionRolesString string + roles, err := services.GuildService.GuildRoles(guild) + if err == nil { + for _, r := range roles { + if r.RoleType == "admin" || r.RoleType == "moderator" { + mentionRolesString += fmt.Sprintf("<@&%s> ", r.ID) + } + } + } + for _, c := range channels { + if c.Admin { + _, _ = ctx.Session.ChannelMessageSend(c.ID, + fmt.Sprintf("%s\n"+ + "New Request ID %d\n"+ + "%s has requested assistance: \n"+ + "```\n%s\n```\n"+ + "Requested At: %s\n"+ + "In: %s", + mentionRolesString, + request.ID, + ctx.Message.Author.Mention(), + request.Content, + request.RequestedAt.UTC().Format(time.UnixDate), + ctx.Channel.Mention(), + ), + ) + } + } + } + _, err = ctx.Send(fmt.Sprintf("%s The admin have recieved your request.\n "+ + "If you would like to close or add a comment to this request please reference ID `%v`", + ctx.Message.Author.Mention(), request.ID, + )) + if err != nil { + discord_utils.SendErrorMessage(ctx, "There was an error sending the message. The request was created.", err) + } +} + +var CloseCommand = &disgoman.Command{ + Name: "close", + Aliases: nil, + Description: "Close a request and mark it as completed.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: closeCommandFunc, +} + +func closeCommandFunc(ctx disgoman.Context, args []string) { + var ids []int64 + var reason string + _, err := strconv.ParseInt(args[len(args)-1], 10, 64) + if err != nil { + reason = args[len(args)-1] + args = args[0 : len(args)-1] + } + for _, a := range args { + a = strings.Trim(a, ",") + id, err := strconv.ParseInt(a, 10, 64) + if err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("%s is not a valid request id", a), err) + continue + } + ids = append(ids, id) + } + if len(ids) == 0 { + discord_utils.SendErrorMessage(ctx, "No requests to close", nil) + return + } + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error getting Guild from the database", err) + return + } + closer, err := services.UserService.GetOrCreateUser(ctx.Message.Author.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error closing the request. Could not get user.", err) + return + } + int64ID, _ := strconv.ParseInt(ctx.Message.ID, 10, 64) + s := discord_utils.ParseSnowflake(int64ID) + for _, id := range ids { + request, err := services.RequestService.Request(id) + if err != nil || request.Guild.ID != guild.ID { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("%d is not a request in this guild.", id), err) + continue + } + if request.Author != closer && !closer.IsStaff && !closer.IsAdmin { + if !discord_utils.IsGuildMod(ctx, closer) && !discord_utils.IsGuildAdmin(ctx, closer) { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("You are not authorized to close %d", id), nil) + continue + } + } + if request.Completed { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("%d is already closed", id), err) + continue + } + request.Completed = true + request.CompletedAt.Valid = true + request.CompletedAt.Time = s.CreationTime + request.CompletedBy = &closer + if reason != "" { + request.CompletedMessage.Valid = true + request.CompletedMessage.String = reason + } + request, err = services.RequestService.UpdateRequest(request) + if err != nil { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("Error closing %d", id), err) + continue + } + _, err = ctx.Send(fmt.Sprintf("%d has been closed", request.ID)) + if err != nil { + discord_utils.SendErrorMessage(ctx, "There was an error sending the message. The request was closed.", err) + } + dmChannel, err := ctx.Session.UserChannelCreate(request.Author.ID) + if err != nil { + return + } + _, _ = ctx.Session.ChannelMessageSend(dmChannel.ID, + fmt.Sprintf("%s has closed request %d which you opened in the %s channel.\n```%s```\n", + discord_utils.GetDisplayName(ctx, request.CompletedBy.ID), + request.ID, + discord_utils.GetChannelName(ctx, request.Channel.ID), + request.Content, + )) + } +} + +var ListCommand = &disgoman.Command{ + Name: "list", + Aliases: []string{"request_list", "requests_list"}, + Description: "List your open requests or all open requests if the caller is a moderator", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: listCommandFunc, +} + +func listCommandFunc(ctx disgoman.Context, args []string) { + user, err := services.UserService.GetOrCreateUser(ctx.Message.Author.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error closing the request. Could not get user.", err) + return + } + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error getting Guild from the database", err) + return + } + + var requests []geeksbot.Request + if discord_utils.IsGuildMod(ctx, user) || discord_utils.IsGuildAdmin(ctx, user) { + requests, err = services.RequestService.GuildRequests(guild, false) + } else { + requests, err = services.RequestService.UserRequests(user, false) + } + + for _, request := range requests { + var authorName string + author, err := ctx.Session.GuildMember(guild.ID, request.Author.ID) + if err != nil { + authorName = "Unknown" + } else { + if author.Nick == "" { + authorName = author.User.Username + } else { + authorName = author.Nick + } + } + + var channelName string + channel, err := ctx.Session.Channel(request.Channel.ID) + if err != nil { + channelName = "Unknown" + } else { + channelName = channel.Name + } + commentCount, err := services.RequestService.RequestCommentCount(request) + if err != nil { + commentCount = 0 + } + _, _ = ctx.Send(fmt.Sprintf("```md\n"+ + "< Request ID Requested By >\n"+ + "< %-11d %23s >\n"+ + "%s\n\n"+ + "Comments: %d\n"+ + "Requested At: %s\n"+ + "In: %s\n"+ + "```", + request.ID, + authorName, + request.Content, + commentCount, + request.RequestedAt.Format("2006-01-02 15:04:05 MST"), + channelName, + )) + } + + _, _ = ctx.Send(fmt.Sprintf("```There %s currently %d open %s```", utils.PluralizeString("is", len(requests)), len(requests), utils.PluralizeString("request", len(requests)))) +} + +var CommentCommand = &disgoman.Command{ + Name: "comment", + Aliases: []string{"update", "add_comment"}, + Description: "Add a comment to an existing request.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: commentCommandFunc, +} + +func commentCommandFunc(ctx disgoman.Context, args []string) { + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error getting Guild from the database", err) + return + } + id, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Please include the ID of the request to update.", err) + return + } + message := strings.Join(args[1:len(args)], " ") + author, err := services.UserService.GetOrCreateUser(ctx.Message.Author.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Sorry, there was an issue finding your user account", err) + return + } + request, err := services.RequestService.Request(id) + if err != nil || request.Guild.ID != guild.ID { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("%d is not a valid request for this guild", id), err) + return + } + int64ID, _ := strconv.ParseInt(ctx.Message.ID, 10, 64) + s := discord_utils.ParseSnowflake(int64ID) + comment := geeksbot.Comment{ + Author: author, + Request: request, + CommentAt: s.CreationTime, + Content: message, + } + comment, err = services.RequestService.CreateComment(comment) + if err != nil { + discord_utils.SendErrorMessage(ctx, "There was a problem adding your comment", err) + return + } + channels, err := services.ChannelService.GuildChannels(guild) + if err == nil { + comments, _ := services.RequestService.RequestComments(request) + var commentString string + var commentStrings []string + commentString = fmt.Sprintf("Comment added:\n```md\n"+ + "< Request ID Requested By >\n"+ + "< %-11d %23s >\n"+ + "%s\n\n"+ + "Comments: Not Implemented Yet\n"+ + "Requested At: %s\n"+ + "In: %s\n"+ + "```", + request.ID, + discord_utils.GetDisplayName(ctx, request.Author.ID), + request.Content, + request.RequestedAt.Format("2006-01-02 15:04:05"), + discord_utils.GetChannelName(ctx, request.Channel.ID), + ) + for _, c := range comments { + if err != nil { + log.Println(err) + continue + } + cs := fmt.Sprintf("```md\n%s\n- %s At %s\n```\n", + c.Content, + discord_utils.GetDisplayName(ctx, c.Author.ID), + c.CommentAt.Format("2006-01-02 15:04:05"), + ) + if len(commentString+cs) >= 2000 { + commentStrings = append(commentStrings, commentString) + commentString = "" + } + commentString += cs + } + commentStrings = append(commentStrings, commentString) + for _, c := range channels { + if c.Admin { + for _, s := range commentStrings { + _, _ = ctx.Session.ChannelMessageSend(c.ID, s) + } + } + } + } else { + log.Println(err) + } + _, err = ctx.Send(fmt.Sprintf("%s your comment has been added.", ctx.Message.Author.Mention())) + dmChannel, err := ctx.Session.UserChannelCreate(request.Author.ID) + if err != nil { + return + } + _, _ = ctx.Session.ChannelMessageSend(dmChannel.ID, + fmt.Sprintf("%s has add a comment to request %d which you opened in the %s channel.\n```%s```\n```%s```", + discord_utils.GetDisplayName(ctx, author.ID), + request.ID, + discord_utils.GetChannelName(ctx, ctx.Channel.ID), + request.Content, + message, + )) + +} + +var ViewCommand = &disgoman.Command{ + Name: "view", + Aliases: nil, + Description: "View the details about a request.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: viewCommandFunc, +} + +func viewCommandFunc(ctx disgoman.Context, args []string) { + guild, err := services.GuildService.GetOrCreateGuild(ctx.Guild.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Error getting Guild from the database", err) + return + } + id, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Please include the ID of the request to view.", err) + return + } + request, err := services.RequestService.Request(id) + if err != nil || request.Guild.ID != guild.ID { + discord_utils.SendErrorMessage(ctx, fmt.Sprintf("%d is not a valid request in this guild", id), err) + return + } + requestor, err := services.UserService.GetOrCreateUser(ctx.Message.Author.ID) + if err != nil { + discord_utils.SendErrorMessage(ctx, "Sorry, there was an issue finding your user account", err) + return + } + if request.Author.ID != ctx.Message.Author.ID && + !discord_utils.IsGuildMod(ctx, requestor) && + !discord_utils.IsGuildAdmin(ctx, requestor) { + discord_utils.SendErrorMessage(ctx, "You are not authorized to view that request", nil) + return + } + comments, err := services.RequestService.RequestComments(request) + if err != nil { + discord_utils.SendErrorMessage(ctx, "There was an error getting the comments.", err) + } + var commentString string + var commentStrings []string + commentString = fmt.Sprintf("```md\n"+ + "< Request ID Requested By >\n"+ + "< %-11d %23s >\n"+ + "%s\n\n"+ + "Requested At: %s\n"+ + "In: %s\n"+ + "```", + request.ID, + discord_utils.GetDisplayName(ctx, request.Author.ID), + request.Content, + request.RequestedAt.Format("2006-01-02 15:04:05"), + discord_utils.GetChannelName(ctx, request.Channel.ID), + ) + for _, c := range comments { + cs := fmt.Sprintf("```md\n%s\n- %s At %s\n```\n", + c.Content, + discord_utils.GetDisplayName(ctx, c.Author.ID), + c.CommentAt.Format("2006-01-02 15:04:05"), + ) + if len(commentString+cs) >= 2000 { + commentStrings = append(commentStrings, commentString) + commentString = "" + } + commentString += cs + } + commentStrings = append(commentStrings, commentString) + for _, c := range commentStrings { + _, _ = ctx.Send(c) + } +} diff --git a/internal/exts/utils/utils.go b/internal/exts/utils/utils.go index 9a2a2a8..a3d5f02 100644 --- a/internal/exts/utils/utils.go +++ b/internal/exts/utils/utils.go @@ -9,7 +9,9 @@ import ( "github.com/bwmarrin/discordgo" "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/geeksbot" "github.com/dustinpianalto/geeksbot/internal/discord_utils" + "github.com/dustinpianalto/geeksbot/pkg/services" ) var PingCommand = &disgoman.Command{ @@ -246,3 +248,34 @@ func userCommandFunc(ctx disgoman.Context, args []string) { } } } + +var AddUserCommand = &disgoman.Command{ + Name: "adduser", + Aliases: nil, + Description: "Get user info", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: addUserCommandFunc, +} + +func addUserCommandFunc(ctx disgoman.Context, args []string) { + if ctx.Message.Author.ID == ctx.CommandManager.Owners[0] { + user := geeksbot.User{ + ID: ctx.Message.Author.ID, + IsActive: true, + IsStaff: true, + IsAdmin: true, + } + user, err := services.UserService.CreateUser(user) + if err != nil { + ctx.CommandManager.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error with adding user", + Error: err, + } + return + } + ctx.Session.MessageReactionAdd(ctx.Channel.ID, ctx.Message.ID, "✅") + } +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..e5f0f43 --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,24 @@ +package utils + +func RemoveDuplicateStrings(s []string) []string { + keys := make(map[string]bool) + o := []string{} + + for _, e := range s { + if _, v := keys[e]; !v { + keys[e] = true + o = append(o, e) + } + } + return o +} + +func PluralizeString(s string, i int) string { + if i == 1 { + return s + } + if s == "is" { + return "are" + } + return s + "s" +} diff --git a/message.go b/message.go new file mode 100644 index 0000000..8ebe4a9 --- /dev/null +++ b/message.go @@ -0,0 +1,24 @@ +package geeksbot + +import ( + "database/sql" + "time" +) + +type Message struct { + ID string + CreatedAt time.Time + ModifiedAt sql.NullTime + Content string + PreviousContent []string + Channel Channel + Author User +} + +type MessageService interface { + Message(id string) (Message, error) + CreateMessage(m Message) (Message, error) + DeleteMessage(m Message) error + ChannelMessages(c Channel) ([]Message, error) + UpdateMessage(m Message) (Message, error) +} diff --git a/patreon.go b/patreon.go new file mode 100644 index 0000000..1a564a3 --- /dev/null +++ b/patreon.go @@ -0,0 +1,34 @@ +package geeksbot + +import "database/sql" + +type PatreonCreator struct { + ID int + Creator string + Link string + Guild Guild +} + +type PatreonTier struct { + ID int + Name string + Description sql.NullString + Creator PatreonCreator + Role Role + NextTier *PatreonTier +} + +type PatreonService interface { + PatreonCreatorByID(id int) (PatreonCreator, error) + PatreonCreatorByName(name string, guild Guild) (PatreonCreator, error) + CreatePatreonCreator(c PatreonCreator) (PatreonCreator, error) + UpdatePatreonCreator(c PatreonCreator) (PatreonCreator, error) + DeletePatreonCreator(c PatreonCreator) error + PatreonTierByID(id int) (PatreonTier, error) + PatreonTierByName(name string, creator string) (PatreonTier, error) + CreatePatreonTier(t PatreonTier) (PatreonTier, error) + UpdatePatreonTier(t PatreonTier) (PatreonTier, error) + DeletePatreonTier(t PatreonTier) error + GuildPatreonCreators(g Guild) ([]PatreonCreator, error) + CreatorPatreonTiers(c PatreonCreator) ([]PatreonTier, error) +} diff --git a/pkg/database/channel.go b/pkg/database/channel.go new file mode 100644 index 0000000..5475442 --- /dev/null +++ b/pkg/database/channel.go @@ -0,0 +1,87 @@ +package database + +import ( + "database/sql" + "log" + + "github.com/dustinpianalto/geeksbot" +) + +type channelService struct { + db *sql.DB +} + +func (s channelService) Channel(id string) (geeksbot.Channel, error) { + var channel geeksbot.Channel + var guild_id string + queryString := "SELECT id, guild_id, admin, default_channel, new_patron FROM channels WHERE id = $1" + row := s.db.QueryRow(queryString, id) + err := row.Scan(&channel.ID, &guild_id, &channel.Admin, &channel.Default, &channel.NewPatron) + if err != nil { + return geeksbot.Channel{}, err + } + guild, err := GuildService.Guild(guild_id) + if err != nil { + return geeksbot.Channel{}, err + } + channel.Guild = guild + return channel, nil +} + +func (s channelService) CreateChannel(c geeksbot.Channel) (geeksbot.Channel, error) { + queryString := "INSERT INTO channels (id, guild_id, admin, default_channel, new_patron) VALUES ($1, $2, $3, $4, $5)" + _, err := s.db.Exec(queryString, c.ID, c.Guild.ID, c.Admin, c.Default, c.NewPatron) + return c, err +} + +func (s channelService) DeleteChannel(c geeksbot.Channel) error { + queryString := "DELETE FROM channels WHERE id = $1" + _, err := s.db.Exec(queryString, c.ID) + return err +} + +func (s channelService) UpdateChannel(c geeksbot.Channel) (geeksbot.Channel, error) { + queryString := "UPDATE channels SET admin = $2, default_channel = $3, new_patron = $4 WHERE id = $1" + _, err := s.db.Exec(queryString, c.ID, c.Admin, c.Default, c.NewPatron) + return c, err +} + +func (s channelService) GuildChannels(g geeksbot.Guild) ([]geeksbot.Channel, error) { + var channels []geeksbot.Channel + queryString := "SELECT id FROM channels WHERE guild_id = $1" + rows, err := s.db.Query(queryString, g.ID) + for rows.Next() { + var id string + err = rows.Scan(&id) + if err != nil { + log.Println(err) + continue + } + channel, err := s.Channel(id) + if err != nil { + log.Println(err) + continue + } + channels = append(channels, channel) + } + return channels, nil +} + +func (s channelService) GetOrCreateChannel(id string, guild_id string) (geeksbot.Channel, error) { + channel, err := s.Channel(id) + if err == sql.ErrNoRows { + var guild geeksbot.Guild + guild, err = GuildService.GetOrCreateGuild(guild_id) + if err != nil { + return geeksbot.Channel{}, err + } + channel, err = s.CreateChannel(geeksbot.Channel{ + ID: id, + Guild: guild, + Admin: false, + Default: false, + NewPatron: false, + }) + } + return channel, err +} diff --git a/pkg/database/database.go b/pkg/database/database.go new file mode 100644 index 0000000..83bddc3 --- /dev/null +++ b/pkg/database/database.go @@ -0,0 +1,76 @@ +package database + +import ( + "database/sql" + "fmt" + "log" + + "github.com/dustinpianalto/geeksbot/pkg/database/migrations" + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/postgres" + bindata "github.com/golang-migrate/migrate/v4/source/go_bindata" +) + +var ( + db *sql.DB + GuildService guildService + UserService userService + ChannelService channelService + MessageService messageService + RequestService requestService + PatreonService patreonService + ServerService serverService +) + +func ConnectDatabase(dbConnString string) { + var err error + db, err = sql.Open("postgres", dbConnString) + if err != nil { + log.Fatal(fmt.Errorf("Can't connect to the database: %w", err)) + } + log.Println("Database Connected.") + db.SetMaxOpenConns(75) + db.SetMaxIdleConns(5) + db.SetConnMaxLifetime(300) + initServices() + log.Println("Services Initialized") +} + +func RunMigrations() { + s := bindata.Resource(migrations.AssetNames(), + func(name string) ([]byte, error) { + return migrations.Asset(name) + }) + d, err := bindata.WithInstance(s) + if err != nil { + log.Fatal(fmt.Errorf("cannot load migrations: %w", err)) + } + instance, err := postgres.WithInstance(db, &postgres.Config{}) + if err != nil { + log.Fatal(fmt.Errorf("cannot create db driver: %w", err)) + } + m, err := migrate.NewWithInstance("go-bindatafoo", d, "postgres", instance) + if err != nil { + log.Fatal(fmt.Errorf("cannot create migration instance: %w", err)) + } + err = m.Up() + if err != nil { + if err.Error() == "no change" { + log.Println(err) + } else { + log.Fatal(err) + } + } + log.Println("Migrations Run") + +} + +func initServices() { + GuildService = guildService{db: db} + UserService = userService{db: db} + ChannelService = channelService{db: db} + MessageService = messageService{db: db} + PatreonService = patreonService{db: db} + RequestService = requestService{db: db} + ServerService = serverService{db: db} +} diff --git a/pkg/database/guild.go b/pkg/database/guild.go new file mode 100644 index 0000000..a234b41 --- /dev/null +++ b/pkg/database/guild.go @@ -0,0 +1,121 @@ +package database + +import ( + "database/sql" + "log" + + "github.com/dustinpianalto/geeksbot" + "github.com/lib/pq" +) + +type guildService struct { + db *sql.DB +} + +func (s guildService) Guild(id string) (geeksbot.Guild, error) { + var g geeksbot.Guild + queryString := "SELECT id, new_patron_message, prefixes FROM guilds WHERE id = $1" + row := s.db.QueryRow(queryString, id) + err := row.Scan(&g.ID, &g.NewPatronMessage, pq.Array(&g.Prefixes)) + if err != nil { + return geeksbot.Guild{}, err + } + return g, nil +} + +func (s guildService) CreateGuild(g geeksbot.Guild) (geeksbot.Guild, error) { + queryString := "INSERT INTO guilds (id, new_patron_message, prefixes) VALUES ($1, $2, $3)" + _, err := s.db.Exec(queryString, g.ID, g.NewPatronMessage, pq.Array(g.Prefixes)) + return g, err +} + +func (s guildService) DeleteGuild(g geeksbot.Guild) error { + queryString := "DELETE FROM guilds WHERE id = $1" + _, err := s.db.Exec(queryString, g.ID) + return err +} + +func (s guildService) UpdateGuild(g geeksbot.Guild) (geeksbot.Guild, error) { + queryString := "UPDATE guilds SET new_patron_message = $2, prefixes = $3 WHERE id = $1" + _, err := s.db.Exec(queryString, g.ID, g.NewPatronMessage, pq.Array(g.Prefixes)) + return g, err +} + +func (s guildService) GuildRoles(g geeksbot.Guild) ([]geeksbot.Role, error) { + var roles []geeksbot.Role + queryString := "SELECT id FROM roles WHERE guild_id = $1" + rows, err := s.db.Query(queryString, g.ID) + if err != nil { + return nil, err + } + for rows.Next() { + var id string + err = rows.Scan(&id) + if err != nil { + log.Println(err) + continue + } + role, err := s.Role(id) + if err != nil { + log.Println(err) + continue + } + roles = append(roles, role) + } + return roles, nil +} + +func (s guildService) CreateRole(r geeksbot.Role) (geeksbot.Role, error) { + queryString := "INSERT INTO roles (id, role_type, guild_id) VALUES ($1, $2, $3)" + _, err := s.db.Exec(queryString, r.ID, r.RoleType, r.Guild.ID) + return r, err +} + +func (s guildService) Role(id string) (geeksbot.Role, error) { + var role geeksbot.Role + var guild_id string + queryString := "SELECT id, role_type, guild_id FROM roles WHERE id = $1" + row := s.db.QueryRow(queryString, id) + err := row.Scan(&role.ID, &role.RoleType, &guild_id) + if err != nil { + return geeksbot.Role{}, err + } + guild, err := s.Guild(guild_id) + if err != nil { + return geeksbot.Role{}, err + } + role.Guild = guild + return role, nil +} + +func (s guildService) UpdateRole(r geeksbot.Role) (geeksbot.Role, error) { + queryString := "UPDATE roles SET role_type = $2 WHERE id = $1" + _, err := s.db.Exec(queryString, r.ID, r.RoleType) + return r, err +} + +func (s guildService) DeleteRole(r geeksbot.Role) error { + queryString := "DELETE FROM roles WHERE id = $1" + _, err := s.db.Exec(queryString, r.ID) + return err +} + +func (s guildService) GetOrCreateGuild(id string) (geeksbot.Guild, error) { + guild, err := s.Guild(id) + if err == sql.ErrNoRows { + guild = geeksbot.Guild{ + ID: id, + Prefixes: []string{}, + } + guild, err = s.CreateGuild(guild) + } + return guild, err +} + +func (s guildService) CreateOrUpdateRole(r geeksbot.Role) (geeksbot.Role, error) { + role, err := s.CreateRole(r) + if err != nil && err.Error() == `pq: duplicate key value violates unique constraint "roles_pkey"` { + role, err = s.UpdateRole(r) + } + return role, err +} diff --git a/pkg/database/message.go b/pkg/database/message.go new file mode 100644 index 0000000..48089c1 --- /dev/null +++ b/pkg/database/message.go @@ -0,0 +1,95 @@ +package database + +import ( + "database/sql" + "log" + + "github.com/dustinpianalto/geeksbot" + "github.com/lib/pq" +) + +type messageService struct { + db *sql.DB +} + +func (s messageService) Message(id string) (geeksbot.Message, error) { + var m geeksbot.Message + var channel_id string + var author_id string + queryString := `SELECT m.id, m.created_at, m.modified_at, m.content, + m.previous_content, m.channel_id, m.author_id + FROM messages as m + WHERE m.id = $1` + row := s.db.QueryRow(queryString, id) + err := row.Scan(&m.ID, &m.CreatedAt, &m.ModifiedAt, &m.Content, + pq.Array(&m.PreviousContent), &channel_id, &author_id) + if err != nil { + return geeksbot.Message{}, err + } + author, err := UserService.User(author_id) + if err != nil { + return geeksbot.Message{}, err + } + m.Author = author + channel, err := ChannelService.Channel(channel_id) + if err != nil { + return geeksbot.Message{}, err + } + m.Channel = channel + return m, nil +} + +func (s messageService) CreateMessage(m geeksbot.Message) (geeksbot.Message, error) { + queryString := `INSERT INTO messages (id, created_at, content, channel_id, author_id) + VALUES ($1, $2, $3, $4, $5)` + _, err := s.db.Exec(queryString, m.ID, m.CreatedAt, m.Content, m.Channel.ID, m.Author.ID) + return m, err +} + +func (s messageService) UpdateMessage(m geeksbot.Message) (geeksbot.Message, error) { + var content string + var previousContent []string + queryString := "SELECT content, previous_content FROM messages WHERE id = $1" + row := s.db.QueryRow(queryString, m.ID) + err := row.Scan(&content, &previousContent) + if err != nil { + return geeksbot.Message{}, err + } + if m.Content != content { + previousContent = append(previousContent, content) + } + queryString = "UPDATE messages SET modified_at = $2, content = $3, previous_content = $4 WHERE id = $1" + _, err = s.db.Exec(queryString, m.ID, m.ModifiedAt, m.Content, previousContent) + m.PreviousContent = previousContent + return m, nil +} + +func (s messageService) DeleteMessage(m geeksbot.Message) error { + queryString := "DELETE FROM messages WHERE id = $1" + _, err := s.db.Exec(queryString, m.ID) + return err +} + +func (s messageService) ChannelMessages(c geeksbot.Channel) ([]geeksbot.Message, error) { + var messages []geeksbot.Message + queryString := `SELECT id FROM messages WHERE channel_id = $1` + rows, err := s.db.Query(queryString, c.ID) + if err != nil { + return nil, err + } + for rows.Next() { + var id string + err = rows.Scan(&id) + if err != nil { + log.Println(err) + continue + } + m, err := s.Message(id) + if err != nil { + log.Println(err) + continue + } + messages = append(messages, m) + } + return messages, nil +} diff --git a/pkg/database/migrations/000001_init_schema.down.sql b/pkg/database/migrations/000001_init_schema.down.sql new file mode 100644 index 0000000..94f394b --- /dev/null +++ b/pkg/database/migrations/000001_init_schema.down.sql @@ -0,0 +1,13 @@ +BEGIN; + DROP TABLE IF EXISTS servers; + DROP TABLE IF EXISTS comments; + DROP TABLE IF EXISTS requests; + DROP TABLE IF EXISTS patreon_tier; + DROP TABLE IF EXISTS patreon_creator; + DROP TABLE IF EXISTS messages; + DROP TABLE IF EXISTS users; + DROP TABLE IF EXISTS channels; + DROP TABLE IF EXISTS roles; + DROP TYPE IF EXISTS role_types; + DROP TABLE IF EXISTS guilds; +COMMIT; diff --git a/pkg/database/migrations/000001_init_schema.up.sql b/pkg/database/migrations/000001_init_schema.up.sql new file mode 100644 index 0000000..418b1d5 --- /dev/null +++ b/pkg/database/migrations/000001_init_schema.up.sql @@ -0,0 +1,174 @@ +BEGIN; + CREATE TABLE IF NOT EXISTS guilds ( + id varchar(30), + new_patron_message varchar(1000), + prefixes varchar(10)[], + PRIMARY KEY(id) + ); + CREATE TYPE role_type as ENUM ( + 'normal', + 'moderator', + 'admin', + 'patreon' + ); + CREATE TABLE IF NOT EXISTS roles ( + id varchar(30), + role_type role_type, + guild_id varchar(30), + PRIMARY KEY(id), + CONSTRAINT fk_guild + FOREIGN KEY(guild_id) + REFERENCES guilds(id) + ON DELETE CASCADE + ); + CREATE TABLE IF NOT EXISTS channels ( + id varchar(30), + guild_id varchar(30), + admin boolean, + default_channel boolean, + new_patron boolean, + PRIMARY KEY(id), + CONSTRAINT fk_guild + FOREIGN KEY(guild_id) + REFERENCES guilds(id) + ON DELETE CASCADE + ); + CREATE TABLE IF NOT EXISTS users ( + id varchar(30), + steam_id varchar(30), + active boolean, + staff boolean, + admin boolean, + PRIMARY KEY(id) + ); + CREATE TABLE IF NOT EXISTS messages ( + id varchar(30), + created_at timestamp, + modified_at timestamp, + content varchar(2000), + previous_content varchar(2000)[], + channel_id varchar(30), + author_id varchar(30), + embed json, + previous_embeds json[], + PRIMARY KEY(id), + CONSTRAINT fk_channel + FOREIGN KEY(channel_id) + REFERENCES channels(id) + ON DELETE CASCADE, + CONSTRAINT fk_user + FOREIGN KEY(author_id) + REFERENCES users(id) + ON DELETE SET NULL + ); + CREATE TABLE IF NOT EXISTS patreon_creator ( + id integer GENERATED ALWAYS AS IDENTITY, + creator varchar(100), + link varchar(200), + guild_id varchar(30), + PRIMARY KEY(id), + CONSTRAINT fk_guild + FOREIGN KEY(guild_id) + REFERENCES guilds(id) + ON DELETE CASCADE + ); + CREATE TABLE IF NOT EXISTS patreon_tier ( + id integer GENERATED ALWAYS AS IDENTITY, + name varchar(100), + description varchar(1000), + creator integer, + role varchar(30), + next_tier integer, + PRIMARY KEY(id), + CONSTRAINT fk_creator + FOREIGN KEY(creator) + REFERENCES patreon_creator(id) + ON DELETE CASCADE, + CONSTRAINT fk_role + FOREIGN KEY(role) + REFERENCES roles(id) + ON DELETE SET NULL, + CONSTRAINT fk_tier + FOREIGN KEY(next_tier) + REFERENCES patreon_tier(id) + ON DELETE SET NULL + ); + CREATE TABLE IF NOT EXISTS requests ( + id integer GENERATED ALWAYS AS IDENTITY, + author_id varchar(30), + channel_id varchar(30), + content varchar(1000), + requested_at timestamp, + completed_at timestamp, + completed boolean, + completed_by varchar(30), + message_id varchar(30), + completed_message varchar(1000), + PRIMARY KEY(id), + CONSTRAINT fk_user + FOREIGN KEY(author_id) + REFERENCES users(id) + ON DELETE CASCADE, + CONSTRAINT fk_channel + FOREIGN KEY(channel_id) + REFERENCES channels(id) + ON DELETE CASCADE, + CONSTRAINT fk_completed_by + FOREIGN KEY(completed_by) + REFERENCES users(id) + ON DELETE SET NULL, + CONSTRAINT fk_message + FOREIGN KEY(message_id) + REFERENCES messages(id) + ON DELETE CASCADE + ); + CREATE TABLE IF NOT EXISTS comments ( + id integer GENERATED ALWAYS AS IDENTITY, + author_id varchar(30), + request_id integer, + comment_at timestamp, + content varchar(1000), + PRIMARY KEY(id), + CONSTRAINT fk_user + FOREIGN KEY(author_id) + REFERENCES users(id) + ON DELETE CASCADE, + CONSTRAINT fk_request + FOREIGN KEY(request_id) + REFERENCES requests(id) + ON DELETE CASCADE + ); + CREATE TABLE IF NOT EXISTS servers ( + id integer GENERATED ALWAYS AS IDENTITY, + name varchar(100), + ip_address varchar(15), + port integer, + password varchar(200), + alerts_channel_id varchar(30), + guild_id varchar(30), + info_channel_id varchar(30), + info_message_id varchar(30), + settings_message_id varchar(30), + PRIMARY KEY(id), + CONSTRAINT fk_alert_channel + FOREIGN KEY(alerts_channel_id) + REFERENCES channels(id) + ON DELETE SET NULL, + CONSTRAINT fk_guild + FOREIGN KEY(guild_id) + REFERENCES guilds(id) + ON DELETE CASCADE, + CONSTRAINT fk_info_channel + FOREIGN KEY(info_channel_id) + REFERENCES channels(id) + ON DELETE SET NULL, + CONSTRAINT fk_info_message + FOREIGN KEY(info_message_id) + REFERENCES messages(id) + ON DELETE SET NULL, + CONSTRAINT fk_settings_message + FOREIGN KEY(settings_message_id) + REFERENCES messages(id) + ON DELETE SET NULL + ); +COMMIT; diff --git a/pkg/database/migrations/000002_add_guild_to_request.down.sql b/pkg/database/migrations/000002_add_guild_to_request.down.sql new file mode 100644 index 0000000..8d02a48 --- /dev/null +++ b/pkg/database/migrations/000002_add_guild_to_request.down.sql @@ -0,0 +1 @@ +ALTER TABLE requests DROP COLUMN guild_id; diff --git a/pkg/database/migrations/000002_add_guild_to_request.up.sql b/pkg/database/migrations/000002_add_guild_to_request.up.sql new file mode 100644 index 0000000..98400af --- /dev/null +++ b/pkg/database/migrations/000002_add_guild_to_request.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE requests + ADD COLUMN guild_id varchar(30) CONSTRAINT fk_guild REFERENCES guilds(id) ON DELETE CASCADE; diff --git a/pkg/database/migrations/000003_set_column_not_null.down.sql b/pkg/database/migrations/000003_set_column_not_null.down.sql new file mode 100644 index 0000000..291b023 --- /dev/null +++ b/pkg/database/migrations/000003_set_column_not_null.down.sql @@ -0,0 +1,48 @@ +BEGIN; + ALTER TABLE users + ALTER COLUMN active DROP NOT NULL, + ALTER COLUMN staff DROP NOT NULL, + ALTER COLUMN admin DROP NOT NULL; + ALTER TABLE channels + ALTER COLUMN guild_id DROP NOT NULL, + ALTER COLUMN admin DROP NOT NULL, + ALTER COLUMN default_channel DROP NOT NULL, + ALTER COLUMN new_patron DROP NOT NULL; + ALTER TABLE messages + ALTER COLUMN created_at DROP NOT NULL, + ALTER COLUMN content DROP NOT NULL, + ALTER COLUMN channel_id DROP NOT NULL, + ALTER COLUMN author_id DROP NOT NULL; + ALTER TABLE messages + ADD COLUMN embed json, + ADD COLUMN previous_embeds json[]; + ALTER TABLE patreon_creators + ALTER COLUMN creator DROP NOT NULL, + ALTER COLUMN link DROP NOT NULL, + ALTER COLUMN guild_id DROP NOT NULL; + ALTER TABLE patreon_creators + RENAME TO patreon_creator; + ALTER TABLE patreon_tiers + ALTER COLUMN name DROP NOT NULL, + ALTER COLUMN creator DROP NOT NULL, + ALTER COLUMN role DROP NOT NULL; + ALTER TABLE patreon_tiers + RENAME TO patreon_tier; + ALTER TABLE requests + ALTER COLUMN author_id DROP NOT NULL, + ALTER COLUMN channel_id DROP NOT NULL, + ALTER COLUMN content DROP NOT NULL, + ALTER COLUMN requested_at DROP NOT NULL, + ALTER COLUMN completed DROP NOT NULL, + ALTER COLUMN message_id DROP NOT NULL, + ALTER COLUMN guild_id DROP NOT NULL; + ALTER TABLE roles + ALTER COLUMN role_type DROP NOT NULL, + ALTER COLUMN guild_id DROP NOT NULL; + ALTER TABLE servers + ALTER COLUMN name DROP NOT NULL, + ALTER COLUMN ip_address DROP NOT NULL, + ALTER COLUMN port DROP NOT NULL, + ALTER COLUMN password DROP NOT NULL, + ALTER COLUMN guild_id DROP NOT NULL; +COMMIT; diff --git a/pkg/database/migrations/000003_set_column_not_null.up.sql b/pkg/database/migrations/000003_set_column_not_null.up.sql new file mode 100644 index 0000000..dd0ba30 --- /dev/null +++ b/pkg/database/migrations/000003_set_column_not_null.up.sql @@ -0,0 +1,48 @@ +BEGIN; + ALTER TABLE users + ALTER COLUMN active SET NOT NULL, + ALTER COLUMN staff SET NOT NULL, + ALTER COLUMN admin SET NOT NULL; + ALTER TABLE channels + ALTER COLUMN guild_id SET NOT NULL, + ALTER COLUMN admin SET NOT NULL, + ALTER COLUMN default_channel SET NOT NULL, + ALTER COLUMN new_patron SET NOT NULL; + ALTER TABLE messages + ALTER COLUMN created_at SET NOT NULL, + ALTER COLUMN content SET NOT NULL, + ALTER COLUMN channel_id SET NOT NULL, + ALTER COLUMN author_id SET NOT NULL; + ALTER TABLE messages + DROP COLUMN embed, + DROP COLUMN previous_embeds; + ALTER TABLE patreon_creator + ALTER COLUMN creator SET NOT NULL, + ALTER COLUMN link SET NOT NULL, + ALTER COLUMN guild_id SET NOT NULL; + ALTER TABLE patreon_creator + RENAME TO patreon_creators; + ALTER TABLE patreon_tier + ALTER COLUMN name SET NOT NULL, + ALTER COLUMN creator SET NOT NULL, + ALTER COLUMN role SET NOT NULL; + ALTER TABLE patreon_tier + RENAME TO patreon_tiers; + ALTER TABLE requests + ALTER COLUMN author_id SET NOT NULL, + ALTER COLUMN channel_id SET NOT NULL, + ALTER COLUMN content SET NOT NULL, + ALTER COLUMN requested_at SET NOT NULL, + ALTER COLUMN completed SET NOT NULL, + ALTER COLUMN message_id SET NOT NULL, + ALTER COLUMN guild_id SET NOT NULL; + ALTER TABLE roles + ALTER COLUMN role_type SET NOT NULL, + ALTER COLUMN guild_id SET NOT NULL; + ALTER TABLE servers + ALTER COLUMN name SET NOT NULL, + ALTER COLUMN ip_address SET NOT NULL, + ALTER COLUMN port SET NOT NULL, + ALTER COLUMN password SET NOT NULL, + ALTER COLUMN guild_id SET NOT NULL; +COMMIT; diff --git a/pkg/database/migrations/000004_add_sar_role_type.down.sql b/pkg/database/migrations/000004_add_sar_role_type.down.sql new file mode 100644 index 0000000..9fe1be3 --- /dev/null +++ b/pkg/database/migrations/000004_add_sar_role_type.down.sql @@ -0,0 +1,14 @@ +BEGIN; + CREATE TYPE role_type_new AS ENUM ( + 'normal', + 'moderator', + 'admin', + 'patreon', + ); + UPDATE roles SET role_type = 'normal' WHERE role_type = 'sar'; + ALTER TABLE roles + ALTER COLUMN roles TYPE role_type_new; + USING (roles::text::role_type_new) + DROP TYPE role_type; + ALTER TYPE role_type_new RENAME TO role_type; +COMMIT; diff --git a/pkg/database/migrations/000004_add_sar_role_type.up.sql b/pkg/database/migrations/000004_add_sar_role_type.up.sql new file mode 100644 index 0000000..0b9c326 --- /dev/null +++ b/pkg/database/migrations/000004_add_sar_role_type.up.sql @@ -0,0 +1 @@ +ALTER TYPE role_type ADD VALUE 'sar'; diff --git a/pkg/database/migrations/000005_remove_null_from_lists.down.sql b/pkg/database/migrations/000005_remove_null_from_lists.down.sql new file mode 100644 index 0000000..8aebab5 --- /dev/null +++ b/pkg/database/migrations/000005_remove_null_from_lists.down.sql @@ -0,0 +1,6 @@ +BEGIN; + ALTER TABLE messages + ALTER COLUMN previous_content DROP NOT NULL; + ALTER TABLE messages + ALTER COLUMN previous_content DROP DEFAULT; +COMMIT; diff --git a/pkg/database/migrations/000005_remove_null_from_lists.up.sql b/pkg/database/migrations/000005_remove_null_from_lists.up.sql new file mode 100644 index 0000000..5299d7f --- /dev/null +++ b/pkg/database/migrations/000005_remove_null_from_lists.up.sql @@ -0,0 +1,6 @@ +BEGIN; + ALTER TABLE messages + ALTER COLUMN previous_content SET NOT NULL; + ALTER TABLE messages + ALTER COLUMN previous_content SET DEFAULT array[]::varchar[]; +COMMIT; diff --git a/pkg/database/migrations/000006_add_ftp_credentials.down.sql b/pkg/database/migrations/000006_add_ftp_credentials.down.sql new file mode 100644 index 0000000..d52ece2 --- /dev/null +++ b/pkg/database/migrations/000006_add_ftp_credentials.down.sql @@ -0,0 +1,6 @@ +BEGIN; + ALTER TABLE servers + DROP COLUMN ftp_port, + DROP COLUMN ftp_username, + DROP COLUMN ftp_password; +COMMIT; diff --git a/pkg/database/migrations/000006_add_ftp_credentials.up.sql b/pkg/database/migrations/000006_add_ftp_credentials.up.sql new file mode 100644 index 0000000..afa9538 --- /dev/null +++ b/pkg/database/migrations/000006_add_ftp_credentials.up.sql @@ -0,0 +1,6 @@ +BEGIN; + ALTER TABLE servers + ADD COLUMN ftp_port int4, + ADD COLUMN ftp_username varchar(200), + ADD COLUMN ftp_password varchar(200); +COMMIT; diff --git a/pkg/database/migrations/bindata.go b/pkg/database/migrations/bindata.go new file mode 100644 index 0000000..f6e9f68 --- /dev/null +++ b/pkg/database/migrations/bindata.go @@ -0,0 +1,267 @@ +// Code generated for package migrations by go-bindata DO NOT EDIT. (@generated) +// sources: +// 000001_init_schema.down.sql +// 000001_init_schema.up.sql +package migrations + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +// Name return file name +func (fi bindataFileInfo) Name() string { + return fi.name +} + +// Size return file size +func (fi bindataFileInfo) Size() int64 { + return fi.size +} + +// Mode return file mode +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} + +// Mode return file modify time +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} + +// IsDir return file whether a directory +func (fi bindataFileInfo) IsDir() bool { + return fi.mode&os.ModeDir != 0 +} + +// Sys return file is sys mode +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var __000001_init_schemaDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\xcf\x3d\x0e\xc2\x30\x0c\x86\xe1\x3d\xa7\xf0\x3d\x32\x51\x08\x28\x12\xa5\x15\xcd\x00\x53\x15\x15\xab\x54\xca\x4f\xb1\x13\x24\x6e\xcf\x0c\x83\x61\xf6\x23\x7d\x7e\x1b\x73\xb0\x27\xad\x00\x00\x76\xe7\xae\x07\xb7\x69\x8e\x06\xec\x1e\xcc\xc5\x0e\x6e\x00\x46\x7a\x22\xb1\x20\xa6\x1c\x23\xa6\x22\x11\xc2\x47\x45\x16\xc9\xea\x0b\x61\x4e\x63\x59\x90\xfe\x60\x13\xa1\x2f\x59\x92\x11\x99\xfd\x8c\xd2\x66\xe5\x1f\x65\x77\x9f\x12\x06\xb1\x2c\x87\xcf\x89\x6b\xff\x7d\x1e\xcb\x6b\x15\xdf\x98\xeb\x12\x6e\xac\xd5\xb6\x6b\x5b\xeb\xb4\x7a\x07\x00\x00\xff\xff\x57\xde\x8f\x03\x93\x01\x00\x00") + +func _000001_init_schemaDownSqlBytes() ([]byte, error) { + return bindataRead( + __000001_init_schemaDownSql, + "000001_init_schema.down.sql", + ) +} + +func _000001_init_schemaDownSql() (*asset, error) { + bytes, err := _000001_init_schemaDownSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "000001_init_schema.down.sql", size: 403, mode: os.FileMode(420), modTime: time.Unix(1611206279, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var __000001_init_schemaUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x58\x5d\x6f\xa3\x38\x14\x7d\xef\xaf\xf0\x5b\x12\x69\x1f\xba\xbb\xda\xa7\x3e\xd1\xc4\xad\xd0\x26\xa4\x22\x54\xbb\xd1\x68\x84\xdc\x70\x93\x7a\x0a\x36\x63\x3b\x69\xfb\xef\x47\x50\x3e\x1c\xc0\xe0\x51\x99\x6a\xca\x53\xe4\x7b\xb1\x8f\xcf\x3d\xf7\x83\x5c\xe3\x5b\xd7\xbb\xba\x40\x08\xa1\xb9\x8f\x9d\x00\xa3\xc0\xb9\x5e\x62\xe4\xde\x20\x6f\x1d\x20\xfc\xbf\xbb\x09\x36\xe8\x70\xa4\x71\x24\xd1\x34\xf7\xcb\x1e\x1a\xa1\x13\x11\xbb\x47\x22\xa6\x7f\x5f\xce\xfe\xa8\xd6\x19\x3c\x87\x29\x51\x82\xb3\x30\x01\x29\xc9\x01\x2a\xbf\x3f\x2f\x2f\x75\xcf\x54\xc0\x9e\xbe\x80\xd4\xec\xb3\x2f\x5f\x6b\xfb\x9d\xef\xae\x1c\x7f\x8b\xfe\xc5\xdb\x29\x8d\x66\xf9\xfa\xec\x1c\xe8\xf6\x0e\x23\xc1\x63\x08\xd5\x6b\x0a\x88\x48\x84\xbd\xfb\x95\x06\x72\xc2\xb8\x48\x48\x3c\xa9\x37\x9d\x24\x3c\x02\x41\x14\x17\xfa\x22\x89\x12\xca\xf4\x85\xec\x06\xc0\xd9\xa4\xeb\xd0\x0e\x76\x32\x0c\x36\xe4\xd4\x58\xab\x5f\xb5\x31\xa7\x38\x34\xbd\xda\x60\xa3\x36\xcc\xd7\xde\x26\xf0\x1d\xd7\x0b\xd0\xfe\x29\xcc\x37\xa9\x6c\xd9\x73\xb3\xf6\xb1\x7b\xeb\xe5\x2f\x96\x47\xcc\xce\x3c\xb2\xc7\xc7\x37\xd8\xc7\xde\x1c\x97\xa1\x9e\x76\x79\xad\x3d\xb4\xc0\x4b\x1c\x60\x34\x77\x36\x73\x67\x81\x2d\xf9\xd9\x3d\x12\xc6\x20\xb6\xa1\xa8\x9f\x85\x3c\x50\xe8\x81\xf3\x18\x08\xab\x97\x23\xd8\x93\x63\xac\xc2\xe2\x9c\xb6\x43\xad\xca\xb6\xed\x53\x33\x7b\x94\x20\x6c\x68\x95\x0a\x48\x62\xa6\x75\xa7\xe8\x09\xda\xd4\x48\x45\xf6\xfb\xf6\xb2\x21\x0a\x16\x09\xdb\x71\x83\xa2\x4a\xd8\x5c\x62\x27\x80\x28\x88\x42\xa2\x90\xa2\x09\x48\x45\x92\xb4\xb6\x26\x3c\xa2\x7b\x6a\x34\xef\x38\x53\xc0\x54\xb5\xf3\x5f\xad\x6a\x74\xa2\xfc\x28\xc3\x4e\x3f\xbd\x2e\x15\x22\x33\x93\x79\x54\x8f\x5c\x18\xcd\x90\x3c\x40\x84\xbe\x49\xce\x3a\x0e\xcf\x8d\x32\xb7\xf6\x94\x42\x93\x44\x0b\x64\x46\x91\xd6\xc8\x7b\x65\x5a\x66\xab\x9d\x50\x4d\x60\x32\x65\x1a\x91\x54\x1c\xf5\x02\xc9\xc5\x3d\x80\x62\x83\x03\xe4\xdd\x2f\x97\x96\x6a\x2b\x0a\x7b\x98\x4b\x89\x8b\x73\xd1\x51\xa6\xe0\x00\x02\xdd\x62\x0f\xfb\x4e\x80\x17\xc8\x59\xfe\xe7\x6c\x37\xc8\xd9\x20\x77\x81\xbd\xc0\x0d\xb6\x0d\x35\x72\xa1\xb7\x37\x2d\x32\x31\x65\x4f\xba\x86\xac\x4b\xdc\xa7\x2e\x47\x25\xbd\x8a\x42\x83\xdb\x9f\x24\x97\x91\x04\x0c\xcc\x46\x20\x77\x82\xa6\x8a\x72\x66\x1a\x2d\xca\xd0\x14\x67\x9e\x37\x60\xd3\xdc\xf2\xa2\xde\x60\xb7\x5e\xb2\xcd\xbe\xb7\x43\xcd\xd9\xf7\x66\xef\x0d\x49\x43\x9e\xef\xcb\xc0\xec\xb2\x46\x34\x99\xb1\x17\x4a\x3e\xd3\x58\x26\x9f\x09\x41\xc6\xa7\x11\x41\xc5\xb8\x15\x23\x99\xe3\xb8\xa5\x40\xc0\xf7\x23\x48\x25\xdf\x53\x03\x06\x6a\xfd\x50\xab\x68\xf6\x9a\x86\x8c\x0b\x84\x3d\x2d\x2d\x49\x63\xb0\xb0\xb7\x7b\x75\xfd\xea\xc3\x6b\x37\xb6\xa2\x2f\xf7\x60\x2f\x77\x18\x98\xf3\x2d\xd3\xe7\x83\xfa\xc5\x40\xce\xfc\x56\x2d\x54\x0f\x92\x19\x91\xe6\x34\x5a\x37\x35\x21\x2a\x42\x6d\x04\x53\x8b\xa6\x17\x4a\x39\xf3\x8d\xfb\x91\xc1\x93\x04\xd8\x2f\xcd\xe7\x22\x21\xc3\x7a\xdf\xb3\x7c\xc8\x8e\xb7\x9c\x3e\x3f\x73\x8e\x14\x2c\x98\x5b\x4b\xc5\x52\x7f\x83\x29\xea\xef\xa8\x22\x90\x20\x4e\xad\x2f\xa2\xd1\x46\x0f\x9a\x86\x24\x8a\x04\x48\xed\x4f\x8b\x7f\xf4\x8f\x08\x2e\x54\x5b\x19\x29\x91\xf2\x99\x8b\xc8\x30\x0e\x92\x18\x84\x92\xe1\x50\xaf\xe8\x9f\x1a\x29\xdb\xf3\xc1\x2d\x72\xa7\xa1\xba\x2e\x41\x29\xca\x0e\x72\xd0\xd1\x52\xb3\xf9\xf5\x06\xeb\x6a\x8b\x84\x31\xca\xeb\x50\x35\xfb\xe0\x19\xda\x04\x43\x8f\x9d\x11\x4d\x23\xc0\x1f\x41\x8f\x2e\x97\x7e\x5c\xe3\x96\xfd\x21\x5c\x4d\x85\x1a\xb1\x75\x48\x79\x4c\x7c\x65\x49\x9a\xaf\x57\x2b\x37\xb8\xba\xf8\x11\x00\x00\xff\xff\xb3\xc0\x11\x7f\x4a\x15\x00\x00") + +func _000001_init_schemaUpSqlBytes() ([]byte, error) { + return bindataRead( + __000001_init_schemaUpSql, + "000001_init_schema.up.sql", + ) +} + +func _000001_init_schemaUpSql() (*asset, error) { + bytes, err := _000001_init_schemaUpSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "000001_init_schema.up.sql", size: 5450, mode: os.FileMode(420), modTime: time.Unix(1611216889, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "000001_init_schema.down.sql": _000001_init_schemaDownSql, + "000001_init_schema.up.sql": _000001_init_schemaUpSql, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "000001_init_schema.down.sql": &bintree{_000001_init_schemaDownSql, map[string]*bintree{}}, + "000001_init_schema.up.sql": &bintree{_000001_init_schemaUpSql, map[string]*bintree{}}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/pkg/database/patreon.go b/pkg/database/patreon.go new file mode 100644 index 0000000..6195ca2 --- /dev/null +++ b/pkg/database/patreon.go @@ -0,0 +1,178 @@ +package database + +import ( + "database/sql" + "log" + + "github.com/dustinpianalto/geeksbot" +) + +type patreonService struct { + db *sql.DB +} + +func (s patreonService) PatreonCreatorByID(id int) (geeksbot.PatreonCreator, error) { + var creator geeksbot.PatreonCreator + var gID string + queryString := "SELECT id, creator, link, guild_id FROM patreon_creator WHERE id = $1" + row := s.db.QueryRow(queryString, id) + err := row.Scan(&creator.ID, &creator.Creator, &creator.Link, &gID) + if err != nil { + return geeksbot.PatreonCreator{}, err + } + guild, err := GuildService.Guild(gID) + if err != nil { + return geeksbot.PatreonCreator{}, err + } + creator.Guild = guild + return creator, nil +} + +func (s patreonService) PatreonCreatorByName(name string, g geeksbot.Guild) (geeksbot.PatreonCreator, error) { + var id int + queryString := "SELECT id FROM patreon_creator WHERE creator = $1 AND guild_id = $2" + err := s.db.QueryRow(queryString, name, g.ID).Scan(&id) + if err != nil { + return geeksbot.PatreonCreator{}, nil + } + creator, err := s.PatreonCreatorByID(id) + return creator, err +} + +func (s patreonService) CreatePatreonCreator(c geeksbot.PatreonCreator) (geeksbot.PatreonCreator, error) { + var id int + queryString := `INSERT INTO patreon_creator (creator, link, guild_id) VALUES ($1, $2, $3) RETURNING id` + err := s.db.QueryRow(queryString, c.Creator, c.Link, c.Guild.ID).Scan(&id) + if err != nil { + return geeksbot.PatreonCreator{}, err + } + c.ID = id + return c, nil +} + +func (s patreonService) UpdatePatreonCreator(c geeksbot.PatreonCreator) (geeksbot.PatreonCreator, error) { + queryString := `UPDATE patreon_creator SET creator = $2, link = $3 WHERE id = $1` + _, err := s.db.Exec(queryString, c.ID, c.Creator, c.Link) + return c, err +} + +func (s patreonService) DeletePatreonCreator(c geeksbot.PatreonCreator) error { + queryString := `DELETE FROM patreon_creator WHERE id = $1` + _, err := s.db.Exec(queryString, c.ID) + return err +} + +func (s patreonService) PatreonTierByID(id int) (geeksbot.PatreonTier, error) { + var tier geeksbot.PatreonTier + var cID int + var rID string + var next int + queryString := `SELECT id, name, description, creator, role, next_tier FROM patreon_tier WHERE id = id` + err := s.db.QueryRow(queryString, id).Scan(&tier.ID, &tier.Name, &tier.Description, &cID, &rID, &next) + if err != nil { + return geeksbot.PatreonTier{}, err + } + creator, err := s.PatreonCreatorByID(cID) + if err != nil { + return geeksbot.PatreonTier{}, err + } + tier.Creator = creator + role, err := GuildService.Role(rID) + if err != nil { + return geeksbot.PatreonTier{}, err + } + tier.Role = role + if next == -1 { + tier.NextTier = nil + return tier, nil + } + nextTier, err := s.PatreonTierByID(next) + if err != nil { + return geeksbot.PatreonTier{}, err + } + tier.NextTier = &nextTier + return tier, nil +} + +func (s patreonService) PatreonTierByName(name string, creator string) (geeksbot.PatreonTier, error) { + var id int + queryString := `SELECT id FROM patreon_tier WHERE name = $1 AND creator = $2` + err := s.db.QueryRow(queryString, name, creator).Scan(&id) + if err != nil { + return geeksbot.PatreonTier{}, err + } + tier, err := s.PatreonTierByID(id) + return tier, err +} + +func (s patreonService) CreatePatreonTier(t geeksbot.PatreonTier) (geeksbot.PatreonTier, error) { + var id int + queryString := `INSERT INTO patreon_tier (name, description, creator, role, next_tier) + VALUES ($1, $2, $3, $4, $5) RETURNING id` + err := s.db.QueryRow(queryString, t.Name, t.Description, t.Creator.ID, t.Role.ID, t.NextTier.ID).Scan(&id) + if err != nil { + return geeksbot.PatreonTier{}, err + } + t.ID = id + return t, nil +} + +func (s patreonService) UpdatePatreonTier(t geeksbot.PatreonTier) (geeksbot.PatreonTier, error) { + queryString := `UPDATE patreon_tier SET name = $2, description = $3, role = $4, next_tier = $5 WHERE id = $1` + _, err := s.db.Exec(queryString, t.ID, t.Name, t.Description, t.Role.ID, t.NextTier.ID) + return t, err +} + +func (s patreonService) DeletePatreonTier(t geeksbot.PatreonTier) error { + queryString := `DELETE FROM patreon_tier WHERE id = $1` + _, err := s.db.Exec(queryString, t.ID) + return err +} + +func (s patreonService) GuildPatreonCreators(g geeksbot.Guild) ([]geeksbot.PatreonCreator, error) { + var creators []geeksbot.PatreonCreator + queryString := `SELECT id FROM patreon_creator WHERE guild_id = $1` + rows, err := s.db.Query(queryString, g.ID) + if err != nil { + return nil, err + } + for rows.Next() { + var id int + err := rows.Scan(&id) + if err != nil { + log.Println(err) + continue + } + creator, err := s.PatreonCreatorByID(id) + if err != nil { + log.Println(err) + continue + } + creators = append(creators, creator) + } + return creators, nil +} + +func (s patreonService) CreatorPatreonTiers(c geeksbot.PatreonCreator) ([]geeksbot.PatreonTier, error) { + var tiers []geeksbot.PatreonTier + queryString := `SELECT id FROM patreon_tier WHERE creator = $1` + rows, err := s.db.Query(queryString, c.ID) + if err != nil { + return nil, err + } + for rows.Next() { + var id int + err := rows.Scan(&id) + if err != nil { + log.Println(err) + continue + } + tier, err := s.PatreonTierByID(id) + if err != nil { + log.Println(err) + continue + } + tiers = append(tiers, tier) + } + return tiers, nil +} diff --git a/pkg/database/request.go b/pkg/database/request.go new file mode 100644 index 0000000..21a7e1c --- /dev/null +++ b/pkg/database/request.go @@ -0,0 +1,238 @@ +package database + +import ( + "database/sql" + "log" + + "github.com/dustinpianalto/geeksbot" +) + +type requestService struct { + db *sql.DB +} + +func (s requestService) Request(id int64) (geeksbot.Request, error) { + var r geeksbot.Request + var aID string + var cID string + var gID string + var uID sql.NullString + var mID string + queryString := `SELECT id, author_id, channel_id, guild_id, content, requested_at, completed, + completed_at, completed_by, message_id, completed_message FROM requests + WHERE id = $1` + row := s.db.QueryRow(queryString, id) + err := row.Scan(&r.ID, &aID, &cID, &gID, &r.Content, &r.RequestedAt, &r.Completed, + &r.CompletedAt, &uID, &mID, &r.CompletedMessage) + if err != nil { + return geeksbot.Request{}, err + } + author, err := UserService.User(aID) + if err != nil { + return geeksbot.Request{}, err + } + guild, err := GuildService.Guild(gID) + if err != nil { + return geeksbot.Request{}, err + } + channel, err := ChannelService.Channel(cID) + if err != nil { + return geeksbot.Request{}, err + } + if !uID.Valid { + r.CompletedBy = nil + } else { + completedBy, err := UserService.User(uID.String) + if err != nil { + return geeksbot.Request{}, err + } + r.CompletedBy = &completedBy + } + message, err := MessageService.Message(mID) + if err != nil { + return geeksbot.Request{}, err + } + r.Author = author + r.Guild = guild + r.Channel = channel + r.Message = message + return r, nil +} + +func (s requestService) UserRequests(u geeksbot.User, completed bool) ([]geeksbot.Request, error) { + var requests []geeksbot.Request + var queryString string + if completed { + queryString = "SELECT id FROM requests WHERE author_id = $1" + } else { + queryString = "SELECT id FROM requests WHERE author_id = $1 AND completed = False" + } + rows, err := s.db.Query(queryString, u.ID) + if err != nil { + return nil, err + } + for rows.Next() { + var id int64 + err = rows.Scan(&id) + if err != nil { + log.Println(err) + continue + } + request, err := s.Request(id) + if err != nil { + log.Println(err) + continue + } + requests = append(requests, request) + } + return requests, nil +} + +func (s requestService) GuildRequests(g geeksbot.Guild, completed bool) ([]geeksbot.Request, error) { + var requests []geeksbot.Request + var queryString string + if completed { + queryString = "SELECT id FROM requests WHERE guild_id = $1" + } else { + queryString = "SELECT id FROM requests WHERE guild_id = $1 AND completed = False" + } + rows, err := s.db.Query(queryString, g.ID) + if err != nil { + return nil, err + } + for rows.Next() { + var id int64 + err = rows.Scan(&id) + if err != nil { + log.Println(err) + continue + } + request, err := s.Request(id) + if err != nil { + log.Println(err) + continue + } + requests = append(requests, request) + } + return requests, nil + +} + +func (s requestService) CreateRequest(r geeksbot.Request) (geeksbot.Request, error) { + queryString := `INSERT INTO requests + (author_id, channel_id, guild_id, content, requested_at, + completed, completed_at, completed_by, message_id, completed_message) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id` + var id int64 + var completedByID sql.NullString + if r.CompletedBy == nil { + completedByID.String = "" + completedByID.Valid = false + } else { + completedByID.String = r.CompletedBy.ID + completedByID.Valid = true + } + err := s.db.QueryRow(queryString, + r.Author.ID, + r.Channel.ID, + r.Guild.ID, + r.Content, + r.RequestedAt, + r.Completed, + r.CompletedAt, + completedByID, + r.Message.ID, + r.CompletedMessage).Scan(&id) + if err != nil { + return geeksbot.Request{}, err + } + r.ID = id + return r, nil +} + +func (s requestService) UpdateRequest(r geeksbot.Request) (geeksbot.Request, error) { + queryString := `UPDATE requests SET + completed = $2, completed_at = $3, completed_by = $4, completed_message = $5 + WHERE id = $1` + _, err := s.db.Exec(queryString, r.ID, r.Completed, r.CompletedAt, r.CompletedBy.ID, r.CompletedMessage) + return r, err +} + +func (s requestService) DeleteRequest(r geeksbot.Request) error { + queryString := "DELETE FROM requests WHERE id = $1" + _, err := s.db.Exec(queryString, r.ID) + return err +} + +func (s requestService) Comment(id int64) (geeksbot.Comment, error) { + var c geeksbot.Comment + var aID string + var rID int64 + queryString := "SELECT id, author_id, request_id, comment_at, content FROM comments WHERE id = $1" + row := s.db.QueryRow(queryString, id) + err := row.Scan(&c.ID, &aID, &rID, &c.CommentAt, &c.Content) + if err != nil { + return geeksbot.Comment{}, err + } + author, err := UserService.User(aID) + if err != nil { + return geeksbot.Comment{}, err + } + c.Author = author + request, err := s.Request(rID) + if err != nil { + return geeksbot.Comment{}, err + } + c.Request = request + return c, nil +} + +func (s requestService) RequestComments(r geeksbot.Request) ([]geeksbot.Comment, error) { + var comments []geeksbot.Comment + queryString := "SELECT id FROM comments WHERE request_id = $1" + rows, err := s.db.Query(queryString, r.ID) + if err != nil { + return nil, err + } + for rows.Next() { + var id int64 + err := rows.Scan(&id) + if err != nil { + log.Println(err) + continue + } + comment, err := s.Comment(id) + if err != nil { + log.Println(err) + continue + } + comments = append(comments, comment) + } + return comments, nil +} + +func (s requestService) RequestCommentCount(r geeksbot.Request) (int, error) { + var count int + queryString := "SELECT COUNT(id) FROM comments WHERE request_id = $1" + row := s.db.QueryRow(queryString, r.ID) + err := row.Scan(&count) + return count, err +} + +func (s requestService) CreateComment(c geeksbot.Comment) (geeksbot.Comment, error) { + queryString := `INSERT INTO comments (author_id, request_id, comment_at, content) + VALUES ($1, $2, $3, $4) RETURNING id` + var id int64 + err := s.db.QueryRow(queryString, c.Author.ID, c.Request.ID, c.CommentAt, c.Content).Scan(&id) + if err != nil { + return geeksbot.Comment{}, err + } + c.ID = id + return c, nil +} + +func (s requestService) DeleteComment(c geeksbot.Comment) error { + queryString := "DELETE FROM comments WHERE id = $1" + _, err := s.db.Exec(queryString, c.ID) + return err +} diff --git a/pkg/database/server.go b/pkg/database/server.go new file mode 100644 index 0000000..72dc817 --- /dev/null +++ b/pkg/database/server.go @@ -0,0 +1,159 @@ +package database + +import ( + "database/sql" + "log" + + "github.com/dustinpianalto/geeksbot" +) + +type serverService struct { + db *sql.DB +} + +func (s serverService) ServerByID(id int) (geeksbot.Server, error) { + var server geeksbot.Server + var guildID string + var aChanID sql.NullString + var iChanID sql.NullString + var iMsgID sql.NullString + var sMsgID sql.NullString + queryString := `SELECT id, name, ip_address, port, password, alerts_channel_id, + guild_id, info_channel_id, info_message_id, settings_message_id, + ftp_port, ftp_username, ftp_password + FROM servers WHERE id = $1` + row := s.db.QueryRow(queryString, id) + err := row.Scan(&server.ID, &server.Name, &server.IPAddr, &server.Port, &server.Password, + &aChanID, &guildID, &iChanID, &iMsgID, &sMsgID, &server.FTPPort, &server.FTPUser, &server.FTPPass) + if err != nil { + return geeksbot.Server{}, err + } + guild, err := GuildService.Guild(guildID) + if err != nil { + return geeksbot.Server{}, err + } + if !aChanID.Valid { + server.AlertsChannel = nil + } else { + alertChannel, err := ChannelService.Channel(aChanID.String) + if err != nil { + return geeksbot.Server{}, err + } + server.AlertsChannel = &alertChannel + } + if !iChanID.Valid { + server.InfoChannel = nil + } else { + infoChannel, err := ChannelService.Channel(iChanID.String) + if err != nil { + return geeksbot.Server{}, err + } + server.InfoChannel = &infoChannel + } + if !iMsgID.Valid { + server.InfoMessage = nil + } else { + infoMessage, err := MessageService.Message(iMsgID.String) + if err != nil { + return geeksbot.Server{}, err + } + server.InfoMessage = &infoMessage + } + if !sMsgID.Valid { + server.SettingsMessage = nil + } else { + settingsMessage, err := MessageService.Message(sMsgID.String) + if err != nil { + return geeksbot.Server{}, err + } + server.SettingsMessage = &settingsMessage + } + server.Guild = guild + return server, nil +} + +func (s serverService) ServerByName(name string, guild geeksbot.Guild) (geeksbot.Server, error) { + var id int + queryString := "SELECT id FROM servers WHERE LOWER(name) = LOWER($1) AND guild_id = $2" + row := s.db.QueryRow(queryString, name, guild.ID) + err := row.Scan(&id) + if err != nil { + return geeksbot.Server{}, err + } + server, err := s.ServerByID(id) + return server, err +} + +func (s serverService) CreateServer(server geeksbot.Server) (geeksbot.Server, error) { + var id int + queryString := `INSERT INTO servers (name, ip_address, port, password, alerts_channel_id, + guild_id, info_channel_id, info_message_id, settings_message_id) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id` + err := s.db.QueryRow(queryString, + server.Name, + server.IPAddr, + server.Port, + server.Password, + server.AlertsChannel, + server.Guild, + server.InfoChannel, + server.InfoMessage, + server.SettingsMessage, + ).Scan(&id) + if err != nil { + return geeksbot.Server{}, err + } + server.ID = id + return server, nil +} + +func (s serverService) DeleteServer(server geeksbot.Server) error { + queryString := `DELETE FROM servers WHERE id = $1` + _, err := s.db.Exec(queryString, server.ID) + return err +} + +func (s serverService) UpdateServer(server geeksbot.Server) (geeksbot.Server, error) { + queryString := `UPDATE servers SET name = $2, ip_address = $3, port = $4, password = $5, + alerts_channel_id = $6, info_channel_id = $7, info_message_id = $8, + settings_message_id = $9, ftp_port = $10, ftp_username = $11, + ftp_password = $12 WHERE id = $1` + _, err := s.db.Exec(queryString, + server.Name, + server.IPAddr, + server.Port, + server.Password, + server.AlertsChannel.ID, + server.InfoChannel.ID, + server.InfoMessage.ID, + server.SettingsMessage.ID, + server.FTPPort, + server.FTPUser, + server.FTPPass, + ) + return server, err +} + +func (s serverService) GuildServers(g geeksbot.Guild) ([]geeksbot.Server, error) { + var servers []geeksbot.Server + queryString := `SELECT id FROM servers WHERE guild_id = $1` + rows, err := s.db.Query(queryString, g.ID) + if err != nil { + return nil, err + } + for rows.Next() { + var id int + err = rows.Scan(&id) + if err != nil { + log.Println(err) + continue + } + server, err := s.ServerByID(id) + if err != nil { + log.Println(err) + continue + } + servers = append(servers, server) + } + return servers, nil +} diff --git a/pkg/database/user.go b/pkg/database/user.go new file mode 100644 index 0000000..c578aeb --- /dev/null +++ b/pkg/database/user.go @@ -0,0 +1,62 @@ +package database + +import ( + "database/sql" + + "github.com/dustinpianalto/geeksbot" +) + +type userService struct { + db *sql.DB +} + +func (s userService) User(id string) (geeksbot.User, error) { + var user geeksbot.User + queryString := "SELECT id, steam_id, active, staff, admin FROM users WHERE id = $1" + row := s.db.QueryRow(queryString, id) + err := row.Scan(&user.ID, &user.SteamID, &user.IsActive, &user.IsStaff, &user.IsAdmin) + return user, err +} + +func (s userService) CreateUser(u geeksbot.User) (geeksbot.User, error) { + queryString := "INSERT INTO users (id, steam_id, active, staff, admin) VALUES ($1, $2, $3, $4, $5)" + var err error + _, err = s.db.Exec(queryString, u.ID, u.SteamID, u.IsActive, u.IsStaff, u.IsAdmin) + return u, err +} + +func (s userService) DeleteUser(u geeksbot.User) error { + queryString := "DELETE FROM users WHERE id = $1" + _, err := s.db.Exec(queryString, u.ID) + return err +} + +func (s userService) UpdateUser(u geeksbot.User) (geeksbot.User, error) { + queryString := "UPDATE users SET steam_id = $2, active = $3, staff = $4, admin = $5 WHERE id = $1" + _, err := s.db.Exec(queryString, u.ID, u.SteamID, u.IsActive, u.IsStaff, u.IsAdmin) + return u, err +} + +func (s userService) GetOrCreateUser(id string) (geeksbot.User, error) { + user, err := s.User(id) + if err == sql.ErrNoRows { + user, err = s.CreateUser(geeksbot.User{ + ID: id, + IsActive: true, + IsAdmin: false, + IsStaff: false, + }) + } + return user, err +} + +func (s userService) GetBySteamID(steamID string) (geeksbot.User, error) { + var id string + queryString := "SELECT id FROM users WHERE steam_id = $1" + err := s.db.QueryRow(queryString, steamID).Scan(&id) + if err != nil { + return geeksbot.User{}, err + } + user, err := s.User(id) + return user, err +} diff --git a/pkg/services/services.go b/pkg/services/services.go new file mode 100644 index 0000000..98c4056 --- /dev/null +++ b/pkg/services/services.go @@ -0,0 +1,26 @@ +package services + +import ( + "github.com/dustinpianalto/geeksbot" + "github.com/dustinpianalto/geeksbot/pkg/database" +) + +var ( + GuildService geeksbot.GuildService + UserService geeksbot.UserService + ChannelService geeksbot.ChannelService + MessageService geeksbot.MessageService + PatreonService geeksbot.PatreonService + RequestService geeksbot.RequestService + ServerService geeksbot.ServerService +) + +func InitializeServices() { + GuildService = database.GuildService + UserService = database.UserService + ChannelService = database.ChannelService + MessageService = database.MessageService + PatreonService = database.PatreonService + RequestService = database.RequestService + ServerService = database.ServerService +} diff --git a/request.go b/request.go new file mode 100644 index 0000000..0c66856 --- /dev/null +++ b/request.go @@ -0,0 +1,42 @@ +package geeksbot + +import ( + "database/sql" + "time" +) + +type Request struct { + ID int64 + Author User + Channel Channel + Guild Guild + Content string + RequestedAt time.Time + Completed bool + CompletedAt sql.NullTime + CompletedBy *User + Message Message + CompletedMessage sql.NullString +} + +type Comment struct { + ID int64 + Author User + Request Request + CommentAt time.Time + Content string +} + +type RequestService interface { + Request(id int64) (Request, error) + UserRequests(u User, completed bool) ([]Request, error) + GuildRequests(g Guild, completed bool) ([]Request, error) + CreateRequest(r Request) (Request, error) + UpdateRequest(r Request) (Request, error) + DeleteRequest(r Request) error + Comment(id int64) (Comment, error) + RequestComments(r Request) ([]Comment, error) + RequestCommentCount(r Request) (int, error) + CreateComment(c Comment) (Comment, error) + DeleteComment(c Comment) error +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..49b9554 --- /dev/null +++ b/server.go @@ -0,0 +1,26 @@ +package geeksbot + +type Server struct { + ID int + Name string + IPAddr string + Port int + Password string + AlertsChannel *Channel + Guild Guild + InfoChannel *Channel + InfoMessage *Message + SettingsMessage *Message + FTPPort int + FTPUser string + FTPPass string +} + +type ServerService interface { + ServerByID(id int) (Server, error) + ServerByName(name string, guild Guild) (Server, error) + CreateServer(s Server) (Server, error) + DeleteServer(s Server) error + UpdateServer(s Server) (Server, error) + GuildServers(g Guild) ([]Server, error) +} diff --git a/user.go b/user.go new file mode 100644 index 0000000..31f31c1 --- /dev/null +++ b/user.go @@ -0,0 +1,20 @@ +package geeksbot + +import "database/sql" + +type User struct { + ID string + SteamID sql.NullString + IsActive bool + IsStaff bool + IsAdmin bool +} + +type UserService interface { + User(id string) (User, error) + CreateUser(u User) (User, error) + DeleteUser(u User) error + UpdateUser(u User) (User, error) + GetOrCreateUser(id string) (User, error) + GetBySteamID(steamID string) (User, error) +}