├── .gitignore ├── env_discord.sample ├── pkg ├── game │ ├── game_test.go │ └── game.go ├── glide.lock └── moves │ └── moves.go ├── img └── example.png ├── docs ├── img │ ├── register_final.png │ ├── register_part_1.png │ ├── register_part_2.png │ ├── register_part_3.png │ ├── register_part_4.png │ └── register_authorize.png ├── game-playbook.md ├── register.md └── deploying.md ├── bin ├── docker_quickstart └── docker_custom ├── vendor └── github.com │ ├── matryer │ └── moq │ │ ├── moq-logo.png │ │ ├── preview.png │ │ ├── pkg │ │ └── moq │ │ │ ├── testpackages │ │ │ ├── imports │ │ │ │ ├── one │ │ │ │ │ └── one.go │ │ │ │ └── two │ │ │ │ │ └── two.go │ │ │ ├── emptyinterface │ │ │ │ └── empty.go │ │ │ ├── variadic │ │ │ │ └── greeter.go │ │ │ ├── vendoring │ │ │ │ ├── user │ │ │ │ │ └── user.go │ │ │ │ └── vendor │ │ │ │ │ └── github.com │ │ │ │ │ └── matryer │ │ │ │ │ └── somerepo │ │ │ │ │ └── code.go │ │ │ ├── channels │ │ │ │ └── example.go │ │ │ ├── dotimport │ │ │ │ └── service.go │ │ │ └── example │ │ │ │ └── example.go │ │ │ ├── template.go │ │ │ ├── importer.go │ │ │ ├── moq.go │ │ │ └── moq_test.go │ │ ├── moq-logo-small.png │ │ ├── .travis.yml │ │ ├── generate │ │ └── generate.go │ │ ├── .gitignore │ │ ├── example │ │ ├── example.go │ │ └── mockpersonstore_test.go │ │ ├── LICENSE │ │ ├── main.go │ │ └── README.md │ └── bwmarrin │ └── discordgo │ ├── docs │ ├── img │ │ └── discordgo.png │ ├── index.md │ └── GettingStarted.md │ ├── examples │ ├── airhorn │ │ ├── airhorn.dca │ │ ├── README.md │ │ └── main.go │ ├── README.md │ ├── mytoken │ │ ├── main.go │ │ └── README.md │ ├── pingpong │ │ ├── README.md │ │ └── main.go │ ├── avatar │ │ ├── README.md │ │ └── main.go │ └── appmaker │ │ ├── README.md │ │ └── main.go │ ├── user_test.go │ ├── .travis.yml │ ├── mkdocs.yml │ ├── types_test.go │ ├── message_test.go │ ├── user.go │ ├── LICENSE │ ├── oauth2_test.go │ ├── types.go │ ├── logging.go │ ├── ratelimit_test.go │ ├── tools │ └── cmd │ │ └── eventhandlers │ │ └── main.go │ ├── oauth2.go │ ├── discord.go │ ├── ratelimit.go │ ├── README.md │ ├── restapi_test.go │ ├── events.go │ ├── event.go │ ├── discord_test.go │ ├── message.go │ └── endpoints.go ├── data ├── game │ ├── fellowship │ │ ├── game.json │ │ └── basic.json │ └── apocalypse_world │ │ ├── gunlugger.json │ │ ├── angel.json │ │ ├── game.json │ │ ├── chopper.json │ │ ├── driver.json │ │ ├── battlebabe.json │ │ ├── brainer.json │ │ └── basic.json └── example │ └── game │ └── example_game │ ├── basic.json │ ├── game.json │ └── simple_playbook.json ├── go.mod ├── CONTRIBUTING.md ├── Dockerfile ├── playbooks ├── gunlugger.json ├── hardholder.json ├── chopper.json ├── driver.json ├── battlebabe.json ├── maestrod.json ├── hocus.json ├── brainer.json ├── savvyhead.json └── angel.json ├── go.sum ├── README.md ├── static └── css │ └── main.css └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .env* 2 | tags 3 | -------------------------------------------------------------------------------- /env_discord.sample: -------------------------------------------------------------------------------- 1 | export DISCORD_TOKEN="" 2 | export CLIENT_ID="" 3 | -------------------------------------------------------------------------------- /pkg/game/game_test.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "testing" 5 | ) 6 | -------------------------------------------------------------------------------- /img/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/img/example.png -------------------------------------------------------------------------------- /docs/img/register_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/docs/img/register_final.png -------------------------------------------------------------------------------- /docs/img/register_part_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/docs/img/register_part_1.png -------------------------------------------------------------------------------- /docs/img/register_part_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/docs/img/register_part_2.png -------------------------------------------------------------------------------- /docs/img/register_part_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/docs/img/register_part_3.png -------------------------------------------------------------------------------- /docs/img/register_part_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/docs/img/register_part_4.png -------------------------------------------------------------------------------- /docs/img/register_authorize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/docs/img/register_authorize.png -------------------------------------------------------------------------------- /bin/docker_quickstart: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run --name apocalyptica --rm --env-file -p8080:8080 ../.env -d gamefiend/apocalyptica 3 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/moq-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/vendor/github.com/matryer/moq/moq-logo.png -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/vendor/github.com/matryer/moq/preview.png -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/testpackages/imports/one/one.go: -------------------------------------------------------------------------------- 1 | package one 2 | 3 | // Thing is just a thing. 4 | type Thing struct{} 5 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/moq-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/vendor/github.com/matryer/moq/moq-logo-small.png -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/testpackages/emptyinterface/empty.go: -------------------------------------------------------------------------------- 1 | package emptyinterface 2 | 3 | // Empty is an empty interface 4 | type Empty interface{} 5 | -------------------------------------------------------------------------------- /bin/docker_custom: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t apocalyptica-custom \ 3 | && docker run --name apocalyptica --rm --env-file -p8080:8080 ../.env -d apocalyptica-cu 4 | stom 5 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/docs/img/discordgo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/vendor/github.com/bwmarrin/discordgo/docs/img/discordgo.png -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/airhorn/airhorn.dca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamefiend/apocalyptica/HEAD/vendor/github.com/bwmarrin/discordgo/examples/airhorn/airhorn.dca -------------------------------------------------------------------------------- /data/game/fellowship/game.json: -------------------------------------------------------------------------------- 1 | { 2 | "Game": { 3 | "Name": "Fellowship", 4 | "Author": "Jacob Randolph", 5 | "Description": "Narrative fantasy adventure", 6 | "Playbooks": [ 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/testpackages/variadic/greeter.go: -------------------------------------------------------------------------------- 1 | package variadic 2 | 3 | import "context" 4 | 5 | // Greeter greets people. 6 | type Greeter interface { 7 | Greet(ctx context.Context, names ...string) string 8 | } 9 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/testpackages/vendoring/user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import "github.com/matryer/somerepo" 4 | 5 | // Service does something good with computers. 6 | type Service interface { 7 | DoSomething(somerepo.SomeType) error 8 | } 9 | -------------------------------------------------------------------------------- /data/example/game/example_game/basic.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!doit", 4 | "Full": "Do it!", 5 | "Miss": "You missed!", 6 | "Hit": "You get to do most of what you want", 7 | "Crit": "You do everything you want and then some." 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gamefiend/apocalyptica 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/bwmarrin/discordgo v0.16.0 7 | github.com/gorilla/websocket v1.2.1-0.20170708072655-462d5c5828f6 // indirect 8 | golang.org/x/crypto v0.0.0-20170706152725-a48ac81e47fd // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/testpackages/channels/example.go: -------------------------------------------------------------------------------- 1 | package channels 2 | 3 | // Queue is a type to be sent down a channel. 4 | type Queue []string 5 | 6 | // Queuer provides a channel example. 7 | type Queuer interface { 8 | Sub(topic string) (<-chan Queue, error) 9 | } 10 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | sudo: false 4 | 5 | go: 6 | - 1.7.x 7 | - 1.8.x 8 | - tip 9 | 10 | before_install: 11 | - go get github.com/golang/lint/golint 12 | 13 | before_script: 14 | - go vet ./... 15 | - golint ./... 16 | 17 | script: 18 | - go test -v ./... 19 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/testpackages/imports/two/two.go: -------------------------------------------------------------------------------- 1 | package two 2 | 3 | import ( 4 | "github.com/matryer/moq/pkg/moq/testpackages/imports/one" 5 | ) 6 | 7 | // DoSomething does something. 8 | type DoSomething interface { 9 | Do(thing one.Thing) error 10 | Another(thing one.Thing) error 11 | } 12 | -------------------------------------------------------------------------------- /pkg/glide.lock: -------------------------------------------------------------------------------- 1 | hash: 3c058d1701f5af9b832c624e8490e19fcc8009c3fcd11902a0034d69f70abb51 2 | updated: 2017-10-03T21:34:45.501143-04:00 3 | imports: 4 | - name: github.com/bwmarrin/discordgo 5 | version: 2fda7ce223a66a5b70b66987c22c3c94d022ee66 6 | - name: github.com/matryer/moq 7 | version: cc6b1a47faa71a50960a3b75e409d5ffaa5d6df4 8 | testImports: [] 9 | -------------------------------------------------------------------------------- /data/example/game/example_game/game.json: -------------------------------------------------------------------------------- 1 | { 2 | "Game": 3 | { 4 | "Name": "Example Game", 5 | "Author": "Example Person", 6 | "Description": "A game to serve as an example.", 7 | "Playbooks": [ 8 | { 9 | "Name": "Simple Character", 10 | "Moves": "simple_playbook.json" 11 | } 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributors welcome! 2 | --- 3 | 4 | If you want to contribute to the project, that's great! 5 | 6 | Right now, we aren't going to add any additional moves to basic.json (going to speak to AW's creator about that first), so please correct text but don't add moves! 7 | 8 | Please make an issue with proposals for change/fix before submitting a PR. 9 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/user_test.go: -------------------------------------------------------------------------------- 1 | package discordgo 2 | 3 | import "testing" 4 | 5 | func TestUser(t *testing.T) { 6 | t.Parallel() 7 | 8 | user := &User{ 9 | Username: "bob", 10 | Discriminator: "8192", 11 | } 12 | 13 | if user.String() != "bob#8192" { 14 | t.Errorf("user.String() == %v", user.String()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.8 2 | STOPSIGNAL SIGTERM 3 | LABEL maintainer="Quinn Murphy" 4 | 5 | WORKDIR /go/src/github.com/gamefiend/apocalyptica 6 | 7 | COPY . . 8 | 9 | RUN curl https://glide.sh/get | sh \ 10 | && glide install \ 11 | && glide up \ 12 | && CGO_ENABLED=0 go-wrapper install 13 | EXPOSE 8080 14 | ENTRYPOINT ["go-wrapper","run"] 15 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/generate/generate.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | // In a terminal, run `go generate` in this directory to have 4 | // it generates the generated.go file. 5 | 6 | //go:generate moq -out generated.go . MyInterface 7 | 8 | // MyInterface is a test interface. 9 | type MyInterface interface { 10 | One() bool 11 | Two() int 12 | Three() string 13 | } 14 | -------------------------------------------------------------------------------- /data/example/game/example_game/simple_playbook.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!simple", 4 | "Full": "Do Something Simple.", 5 | "Miss": "Your move is too simple and flops.", 6 | "Hit": "Your move is just simple enough to get things going.", 7 | "Crit": "Simplicity is just what we needed! You cut through the fog." 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.6 4 | - 1.7 5 | - 1.8 6 | install: 7 | - go get github.com/bwmarrin/discordgo 8 | - go get -v . 9 | - go get -v github.com/golang/lint/golint 10 | script: 11 | - diff <(gofmt -d .) <(echo -n) 12 | - go vet -x ./... 13 | - golint ./... 14 | - go test -v -race ./... 15 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/testpackages/vendoring/vendor/github.com/matryer/somerepo/code.go: -------------------------------------------------------------------------------- 1 | // Package somerepo is a vendored package to test how moq deals with 2 | // packages in the vendor package. 3 | package somerepo 4 | 5 | // SomeType is just some old type. 6 | type SomeType struct { 7 | // Truth indicates whether true is true or not. Computers. 8 | Truth bool 9 | } 10 | -------------------------------------------------------------------------------- /playbooks/gunlugger.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!fts", 4 | "Full": "Fuck This Shit", 5 | "Miss": "You're caught half in, and half out. Stuck in the breeze.", 6 | "Hit": "You can go or stay, but if you go it's gonna cost: leave something behind, or take something with you. The MC tells you what.", 7 | "Crit": "This shit, has been fucked. You're gone." 8 | }, 9 | ] -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # DiscordGo Examples 4 | 5 | These examples demonstrate how to utilize DiscordGo. 6 | 7 | Please explore the individual folders and give them a try! 8 | 9 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) 10 | Discord chat channel for support.** 11 | 12 | -------------------------------------------------------------------------------- /data/game/apocalypse_world/gunlugger.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!fts", 4 | "Full": "Fuck This Shit", 5 | "Miss": "You're caught half in, and half out. Stuck in the breeze.", 6 | "Hit": "You can go or stay, but if you go it's gonna cost: leave something behind, or take something with you. The MC tells you what.", 7 | "Crit": "This shit, has been fucked. You're gone." 8 | }, 9 | ] -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | .vscode 26 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/testpackages/dotimport/service.go: -------------------------------------------------------------------------------- 1 | // Package dotimport addresses issue 21. 2 | package dotimport 3 | 4 | //go:generate moq -out service_moq_test.go -pkg dotimport_test . Service 5 | 6 | // Service is the interface which should be mocked by moq 7 | type Service interface { 8 | User(ID string) (User, error) 9 | } 10 | 11 | // User is just a struct for testing 12 | type User struct { 13 | Name string 14 | } 15 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: DiscordGo 2 | site_author: Bruce Marriner 3 | site_url: http://bwmarrin.github.io/discordgo/ 4 | repo_url: https://github.com/bwmarrin/discordgo 5 | 6 | dev_addr: 0.0.0.0:8000 7 | theme: yeti 8 | 9 | markdown_extensions: 10 | - smarty 11 | - toc: 12 | permalink: True 13 | - sane_lists 14 | 15 | pages: 16 | - 'Home': 'index.md' 17 | - 'Getting Started': 'GettingStarted.md' 18 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/testpackages/example/example.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import "context" 4 | 5 | // Person is a person. 6 | type Person struct { 7 | ID string 8 | Name string 9 | Company string 10 | Website string 11 | } 12 | 13 | // PersonStore stores people. 14 | type PersonStore interface { 15 | Get(ctx context.Context, id string) (*Person, error) 16 | Create(ctx context.Context, person *Person, confirm bool) error 17 | ClearCache(id string) 18 | } 19 | -------------------------------------------------------------------------------- /data/game/apocalypse_world/angel.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Playbook": "Angel" 4 | }, 5 | { 6 | "Name": "!healingtouch", 7 | "Full": "Healing Touch", 8 | "Miss": "You heal nothing. Both you and the person you lied to about healing have to openbrain except you've already missed the roll", 9 | "Hit": "You put some guts back, heal 1 segment but you gotta openbrain now and hope the Pyschic Maelstrom plays nice.", 10 | "Crit": "You put lots of guts back. heal 1 segment." 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/example/example.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import "context" 4 | 5 | //go:generate moq -out mockpersonstore_test.go . PersonStore 6 | 7 | // Person represents a real person. 8 | type Person struct { 9 | ID string 10 | Name string 11 | Company string 12 | Website string 13 | } 14 | 15 | // PersonStore provides access to Person objects. 16 | type PersonStore interface { 17 | Get(ctx context.Context, id string) (*Person, error) 18 | Create(ctx context.Context, person *Person, confirm bool) error 19 | } 20 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/types_test.go: -------------------------------------------------------------------------------- 1 | package discordgo 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestTimestampParse(t *testing.T) { 9 | ts, err := Timestamp("2016-03-24T23:15:59.605000+00:00").Parse() 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | if ts.Year() != 2016 || ts.Month() != time.March || ts.Day() != 24 { 14 | t.Error("Incorrect date") 15 | } 16 | if ts.Hour() != 23 || ts.Minute() != 15 || ts.Second() != 59 { 17 | t.Error("Incorrect time") 18 | } 19 | 20 | _, offset := ts.Zone() 21 | if offset != 0 { 22 | t.Error("Incorrect timezone") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bwmarrin/discordgo v0.16.0 h1:/HhaLf7VXwJe/zcN+i/tKIbhKa1Y9Xy0uFXHyiDm7TU= 2 | github.com/bwmarrin/discordgo v0.16.0/go.mod h1:5NIvFv5Z7HddYuXbuQegZ684DleQaCFqChP2iuBivJ8= 3 | github.com/gorilla/websocket v1.2.1-0.20170708072655-462d5c5828f6 h1:QuyU2q1n/YHzmBCRpKpnzhGsn3rX8iCggRXfHhaM2uY= 4 | github.com/gorilla/websocket v1.2.1-0.20170708072655-462d5c5828f6/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 5 | golang.org/x/crypto v0.0.0-20170706152725-a48ac81e47fd h1:JtQxzQbegepfaE8AMqkRsccfcZjW0A8qrg/0R2lc8Us= 6 | golang.org/x/crypto v0.0.0-20170706152725-a48ac81e47fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 7 | -------------------------------------------------------------------------------- /playbooks/hardholder.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!leadership", 4 | "Full": "Leadership", 5 | "Miss": "They're going to do it, but you're going to hear about it later. ", 6 | "Hit": "They do what you command.", 7 | "Crit": "Battlefield voice makes them snap to; they do what you want and take +1forward doing it ma'amsirma'am." 8 | }, 9 | 10 | { 11 | "Name": "!wealth", 12 | "Full": "Wealth", 13 | "Miss": "You don't have enough. What you want depends on your holding.", 14 | "Hit": "You have a surplus in one area, but a want in another", 15 | "Crit": "You have surplus at hand and available for needs of the session. They build a monument in your honor (not really, no)." 16 | }, 17 | ] -------------------------------------------------------------------------------- /data/game/apocalypse_world/game.json: -------------------------------------------------------------------------------- 1 | { 2 | "Game": 3 | { 4 | "Name": "Apocalypse World", 5 | "Description": "This is what we've got, yes. What are you going to make of it?", 6 | "Playbooks": [ 7 | { 8 | "Name": "The Angel", 9 | "Moves": "angel.json" 10 | }, 11 | { 12 | "Name": "The Battlebabe", 13 | "Moves": "battlebabe.json" 14 | }, 15 | { 16 | "Name": "The Brainer", 17 | "Moves": "brainer.json" 18 | }, 19 | { "Name": "The Chopper", 20 | "Moves": "chopper.json" 21 | }, 22 | { 23 | "Name": "The Driver", 24 | "Moves": "driver.json" 25 | }, 26 | { 27 | "Name": "The Gunlugger", 28 | "Moves": "gunlugger.json" 29 | } 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/mytoken/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/bwmarrin/discordgo" 9 | ) 10 | 11 | // Variables used for command line parameters 12 | var ( 13 | Email string 14 | Password string 15 | ) 16 | 17 | func init() { 18 | 19 | flag.StringVar(&Email, "e", "", "Account Email") 20 | flag.StringVar(&Password, "p", "", "Account Password") 21 | flag.Parse() 22 | 23 | if Email == "" || Password == "" { 24 | flag.Usage() 25 | os.Exit(1) 26 | } 27 | } 28 | 29 | func main() { 30 | 31 | // Create a new Discord session using the provided login information. 32 | dg, err := discordgo.New(Email, Password) 33 | if err != nil { 34 | fmt.Println("error creating Discord session,", err) 35 | return 36 | } 37 | 38 | // Print out your token. 39 | fmt.Printf("Your Authentication Token is:\n\n%s\n", dg.Token) 40 | } 41 | -------------------------------------------------------------------------------- /playbooks/chopper.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!packalpha", 4 | "Full": "Pack Alpha", 5 | "Miss": "Someone in your gang makes a bid, idle or serious, to replace you for alpha. Are you going to take that?", 6 | "Hit": "Choose 1. They do what you want, they don't fight back over it, you don't have to make an example of one of them.", 7 | "Crit": "Take everything. They do what you want, you don't get any lip, and you dn't have to make an example out of anyone." 8 | }, 9 | 10 | { 11 | "Name": "!fuckingthieves", 12 | "Full": "Fucking Thieves", 13 | "Miss": "You know, you had that thing. Too bad some asswipe stole it from you.", 14 | "Hit": "One of you happens to have something pretty close, unless it's hi-tech then no dice.", 15 | "Crit": "One of you have the thing, or close enough that it doesn't matter." 16 | }, 17 | ] 18 | -------------------------------------------------------------------------------- /playbooks/driver.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!eyeonthedoor", 4 | "Full": "Eye On the Door", 5 | "Miss": "You're caught vulnerable, half in and half out", 6 | "Hit": "You can go, or stay, but if you go it will cost you: leave something behind or take something with you, the MC will tell you what.", 7 | "Crit": "You're gone. Puff of smoke, like real magic shit." 8 | }, 9 | 10 | { 11 | "Name": "!reputation", 12 | "Full": "Reputation", 13 | "Miss": "They've heard of you all right. The MC will tell you what they heard. Probably nothing good cho'.", 14 | "Hit": "They've heard of you, and you say what they've heard; the MC will make them respond accordingly.", 15 | "Crit": "They've heard of you, and you say what they've heard; the MC will make them respond accordingly, and you get +1forward for dealing with them." 16 | }, 17 | ] 18 | -------------------------------------------------------------------------------- /data/game/apocalypse_world/chopper.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!packalpha", 4 | "Full": "Pack Alpha", 5 | "Miss": "Someone in your gang makes a bid, idle or serious, to replace you for alpha. Are you going to take that?", 6 | "Hit": "Choose 1. They do what you want, they don't fight back over it, you don't have to make an example of one of them.", 7 | "Crit": "Take everything. They do what you want, you don't get any lip, and you dn't have to make an example out of anyone." 8 | }, 9 | 10 | { 11 | "Name": "!fuckingthieves", 12 | "Full": "Fucking Thieves", 13 | "Miss": "You know, you had that thing. Too bad some asswipe stole it from you.", 14 | "Hit": "One of you happens to have soemthing pretty close, unless it's hi-tech then no dice.", 15 | "Crit": "One of you have the thing, or close enough that it doesn't matter." 16 | }, 17 | ] -------------------------------------------------------------------------------- /playbooks/battlebabe.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!dangeroussexy", 4 | "Full": "Dangerous & Sexy", 5 | "Miss": "Your enemies identify you immediately as their foremost threat!", 6 | "Hit": "Hold 1. Spend 1 hold to: \n* make eye contact with an NPC present, who freezes or flinches and can't take action until you break it off.", 7 | "Crit": "Hold 2. Spend 1 hold to: \n* make eye contact with an NPC present, who freezes or flinches and can't take action until you break it off." 8 | }, 9 | 10 | { 11 | "Name": "!visionsofdeath", 12 | "Full": "Visions of Death", 13 | "Miss": "You forsee your own death, and take -1 throughout the battle", 14 | "Hit": "Name one NPC who'll die OR one NPC who'll live. The MC will make your visions come true, if it's even remotely possible", 15 | "Crit": "Name one person who'll die OR one person who'll live" 16 | }, 17 | ] 18 | -------------------------------------------------------------------------------- /data/game/apocalypse_world/driver.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!eyeonthedoor", 4 | "Full": "Eye On the Door", 5 | "Miss": "You're caught vulnerable, half in and half out", 6 | "Hit": "You can go, or stay, but if you go it will cost you: leave something behind or take something with you, the MC will tell you what.", 7 | "Crit": "Your gone. Puff of smoke, like real magic shit." 8 | }, 9 | 10 | { 11 | "Name": "!reputation", 12 | "Full": "Reputation", 13 | "Miss": "They've heard of you all right. The MC will tell you what they heard. Probably nothing good cho'.", 14 | "Hit": "They've heard of you, and you say what they've heard; the MC will make them respond accordingly.", 15 | "Crit": "They've heard of you, and you say what they've heard; the MC will make them respond accordingly, and you get +1forward for dealing with them." 16 | }, 17 | ] -------------------------------------------------------------------------------- /data/game/apocalypse_world/battlebabe.json: -------------------------------------------------------------------------------- 1 | [ 2 | "Playbook": "The BattleBabe", 3 | { 4 | "Name": "!dangeroussexy", 5 | "Full": "Dangerous & Sexy", 6 | "Miss": "Your enemies identify you immediately as their foremost threat!", 7 | "Hit": "Hold 1. Spend 1 hold to: \n* make eye contact with an NPC present, who freezes or flinches and can't take action until you break it off.", 8 | "Crit": "Hold 2. Spend 1 hold to: \n* make eye contact with an NPC present, who freezes or flinches and can't take action until you break it off." 9 | }, 10 | 11 | { 12 | "Name": "!visionsofdeath", 13 | "Full": "Visions of Death", 14 | "Miss": "You forsee your own death, and take -1 throughout the battle", 15 | "Hit": "Name one NPC who'll die OR one NPC who'll live. The MC will make your visions come true, if it's even remotely possible", 16 | "Crit": "Name one person who'll die OR one person who'll live" 17 | }, 18 | ] 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Apocalyptica 2 | --- 3 | 4 | ## An Apocalypse World 2e Discord Bot. 5 | 6 | [Apocalypse World](http://apocalypse-world.com) is a great game by Vincent Baker. This bot makes playing the game online easier by providing a dice-roller that also outputs the details of a hit or miss: 7 | 8 | ![Example Usage](img/example.png) 9 | 10 | ### Usage 11 | ``` 12 | !moves - display supported moves (only the basic moves are included) 13 | !!help - display usage 14 | 15 | ! - rolls and displays move results. 16 | ``` 17 | 18 | 19 | ### Use it Now 20 | 21 | Want to try an instance of Apocalyptica on your server? [Try it now](https://apocalyptica.social-fiction.net). 22 | 23 | ### Roll Your Own 24 | 25 | If you want to use your own server, you will first need to [register your app](docs/register.md), then look at the [different ways you can deploy](docs/deploying.md) to Apocalyptica. 26 | 27 | #### Adding your own moves. 28 | 29 | Add your own moves by adding [your own games and playbooks](docs/game-playbook.md) 30 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/message_test.go: -------------------------------------------------------------------------------- 1 | package discordgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestContentWithMoreMentionsReplaced(t *testing.T) { 8 | s := &Session{StateEnabled: true, State: NewState()} 9 | 10 | user := &User{ 11 | ID: "user", 12 | Username: "User Name", 13 | } 14 | 15 | s.StateEnabled = true 16 | s.State.GuildAdd(&Guild{ID: "guild"}) 17 | s.State.RoleAdd("guild", &Role{ 18 | ID: "role", 19 | Name: "Role Name", 20 | Mentionable: true, 21 | }) 22 | s.State.MemberAdd(&Member{ 23 | User: user, 24 | Nick: "User Nick", 25 | GuildID: "guild", 26 | }) 27 | s.State.ChannelAdd(&Channel{ 28 | Name: "Channel Name", 29 | GuildID: "guild", 30 | ID: "channel", 31 | }) 32 | m := &Message{ 33 | Content: "<&role> <@!user> <@user> <#channel>", 34 | ChannelID: "channel", 35 | MentionRoles: []string{"role"}, 36 | Mentions: []*User{user}, 37 | } 38 | if result, _ := m.ContentWithMoreMentionsReplaced(s); result != "@Role Name @User Nick @User Name #Channel Name" { 39 | t.Error(result) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/game-playbook.md: -------------------------------------------------------------------------------- 1 | # Games and Playbook Format 2 | 3 | You can add your own *Powered By the Apocalypse* game to Apocalyptica's list. 4 | 5 | Apocalyptica reads its list of games out of `data/game`. 6 | 7 | Each game has its own directory. containing files: 8 | 9 | - `game.json`: contains game metadata like Name, Author, and a list of playbooks. 10 | - `basic.json`: Basic moves for the game are always in this file. 11 | - `.json`: 1 or more files, each with moves specific to playbooks for the game. 12 | 13 | a skeleton structure that can be copied and applied is in `data/example/game/example_game`. Copy that directory into `data/game`, rename and modify as you need. 14 | 15 | # Contributing a Game 16 | 17 | We would love to support many PbtA games out of the box! More importantly, though, we want to honor the wishes of creators. Please file an issue with "[GAME] My Game" in the title **before** submitting a PR and tell us: 18 | 19 | - info about the game (Name, publisher, short description) 20 | - if there is permission from the creator of the game to use it, with some proof. 21 | 22 | Thanks in advance! 23 | 24 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Mat Ryer and David Hernandez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/pingpong/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## DiscordGo Ping Pong Example 4 | 5 | This example demonstrates how to utilize DiscordGo to create a Ping Pong Bot. 6 | 7 | This Bot will respond to "ping" with "Pong!" and "pong" with "Ping!". 8 | 9 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) 10 | Discord chat channel for support.** 11 | 12 | ### Build 13 | 14 | This assumes you already have a working Go environment setup and that 15 | DiscordGo is correctly installed on your system. 16 | 17 | 18 | From within the pingpong example folder, run the below command to compile the 19 | example. 20 | 21 | ```sh 22 | go build 23 | ``` 24 | 25 | ### Usage 26 | 27 | This example uses bot tokens for authentication only. While user/password is 28 | supported by DiscordGo, it is not recommended for bots. 29 | 30 | ``` 31 | ./pingpong --help 32 | Usage of ./pingpong: 33 | -t string 34 | Bot Token 35 | ``` 36 | 37 | The below example shows how to start the bot 38 | 39 | ```sh 40 | ./pingpong -t YOUR_BOT_TOKEN 41 | Bot is now running. Press CTRL-C to exit. 42 | ``` 43 | -------------------------------------------------------------------------------- /playbooks/maestrod.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!everybodyeats", 4 | "Full": "Everbody eats, even that guy", 5 | "Miss": "Ask the MC 1 question about someone, but that person knows you're looking in on them", 6 | "Hit": "Ask the MC 1 question.\n* How are they doing? what's up with them?\n* What or who do they love best\n* Who do they know, like and/or trust?\n* When next should I expect to see them?\n* How could I get to them, physically or emotionally?", 7 | "Crit": "Ask the MC 3 questions.\n* How are they doing? what's up with them?\n* What or who do they love best\n* Who do they know, like and/or trust?\n* When next should I expect to see them?\n* How could I get to them, physically or emotionally?" 8 | }, 9 | 10 | { 11 | "Name": "!motive", 12 | "Full": "Just give me a motive", 13 | "Miss": "Not very discriminating are you? The MC chooses several people (maybe your target, maybe not) and they get it. It being 3-harm (ap)", 14 | "Hit": "Someone's going to be feeling bad. Your target suffers 2-harm (ap) in the next 24 hours.", 15 | "Crit": "You really don't like them. Your target's insides are slowly turning to mush. They take 4-harm (ap) in the next 24 hours." 16 | }, 17 | ] -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | 12 | "github.com/matryer/moq/pkg/moq" 13 | ) 14 | 15 | func main() { 16 | var err error 17 | defer func() { 18 | if err != nil { 19 | fmt.Fprintln(os.Stderr, err) 20 | flag.Usage() 21 | os.Exit(1) 22 | 23 | } 24 | }() 25 | var ( 26 | outFile = flag.String("out", "", "output file (default stdout)") 27 | pkgName = flag.String("pkg", "", "package name (default will infer)") 28 | ) 29 | flag.Usage = func() { 30 | fmt.Println(`moq [flags] destination interface [interface2 [interface3 [...]]]`) 31 | flag.PrintDefaults() 32 | } 33 | flag.Parse() 34 | args := flag.Args() 35 | if len(args) < 2 { 36 | err = errors.New("not enough arguments") 37 | return 38 | } 39 | destination := args[0] 40 | args = args[1:] 41 | var buf bytes.Buffer 42 | var out io.Writer 43 | out = os.Stdout 44 | if len(*outFile) > 0 { 45 | out = &buf 46 | } 47 | m, err := moq.New(destination, *pkgName) 48 | if err != nil { 49 | return 50 | } 51 | err = m.Mock(out, args...) 52 | if err != nil { 53 | return 54 | } 55 | // create the file 56 | if len(*outFile) > 0 { 57 | err = ioutil.WriteFile(*outFile, buf.Bytes(), 0777) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/user.go: -------------------------------------------------------------------------------- 1 | package discordgo 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // A User stores all data for an individual Discord user. 9 | type User struct { 10 | ID string `json:"id"` 11 | Email string `json:"email"` 12 | Username string `json:"username"` 13 | Avatar string `json:"avatar"` 14 | Discriminator string `json:"discriminator"` 15 | Token string `json:"token"` 16 | Verified bool `json:"verified"` 17 | MFAEnabled bool `json:"mfa_enabled"` 18 | Bot bool `json:"bot"` 19 | } 20 | 21 | // String returns a unique identifier of the form username#discriminator 22 | func (u *User) String() string { 23 | return fmt.Sprintf("%s#%s", u.Username, u.Discriminator) 24 | } 25 | 26 | // Mention return a string which mentions the user 27 | func (u *User) Mention() string { 28 | return fmt.Sprintf("<@%s>", u.ID) 29 | } 30 | 31 | // AvatarURL returns a URL to the user's avatar. 32 | // size: The size of the user's avatar as a power of two 33 | func (u *User) AvatarURL(size string) string { 34 | var URL string 35 | if strings.HasPrefix(u.Avatar, "a_") { 36 | URL = EndpointUserAvatarAnimated(u.ID, u.Avatar) 37 | } else { 38 | URL = EndpointUserAvatar(u.ID, u.Avatar) 39 | } 40 | 41 | return URL + "?size=" + size 42 | } 43 | -------------------------------------------------------------------------------- /playbooks/hocus.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!fortunes", 4 | "Full": "Fortunes", 5 | "Miss": "Your followers are in want. They look to you to aid to provide succor.", 6 | "Hit": "Your followers have a surplus! Yay! They also have a want. Boo.", 7 | "Crit": "Your followers have a surplus! Everyone is happy-ish! If it's barter, then that's your share as the leader and soothsayer to your followers as is only right." 8 | }, 9 | 10 | { 11 | "Name": "!frenzy", 12 | "Full": "Frenzy", 13 | "Miss": "They look amongst themselves, and then turn on you.", 14 | "Hit": "Hold 1. Spend a hold 1 for 1 to make the mob:\n* Bring people forward and deliver them\n* Bring forward all their precious things\n* Unite and fight for you as a gang (2-harm 0-armor size appropriate)\n* Fall into an orgy of uninhibited emotion: fucking, lamenting, fighting, sharing, celebrating, as you choose\n* Go quietly back to their lives", 15 | "Crit": "Hold 3. Spend a hold 1 for 1 to make the mob:\n* Bring people forward and deliver them\n* Bring forward all their precious things\n* Unite and fight for you as a gang (2-harm 0-armor size appropriate)\n* Fall into an orgy of uninhibited emotion: fucking, lamenting, fighting, sharing, celebrating, as you choose\n* Go quietly back to their lives" 16 | }, 17 | ] -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/avatar/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## DiscordGo Avatar Example 4 | 5 | This example demonstrates how to utilize DiscordGo to change the avatar for 6 | a Discord account. This example works both with a local file or the URL of 7 | an image. 8 | 9 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) 10 | Discord chat channel for support.** 11 | 12 | ### Build 13 | 14 | This assumes you already have a working Go environment setup and that 15 | DiscordGo is correctly installed on your system. 16 | 17 | From within the avatar example folder, run the below command to compile the 18 | example. 19 | 20 | ```sh 21 | go build 22 | ``` 23 | 24 | ### Usage 25 | 26 | This example uses bot tokens for authentication only. While email/password is 27 | supported by DiscordGo, it is not recommended to use them. 28 | 29 | ``` 30 | ./avatar --help 31 | Usage of ./avatar: 32 | -f string 33 | Avatar File Name 34 | -t string 35 | Bot Token 36 | -u string 37 | URL to the avatar image 38 | ``` 39 | 40 | The below example shows how to set your Avatar from a local file. 41 | 42 | ```sh 43 | ./avatar -t TOKEN -f avatar.png 44 | ``` 45 | The below example shows how to set your Avatar from a URL. 46 | 47 | ```sh 48 | ./avatar -t TOKEN -u http://bwmarrin.github.io/discordgo/img/discordgo.png 49 | ``` 50 | -------------------------------------------------------------------------------- /playbooks/brainer.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!deepbrain", 4 | "Full": "Deep Brain Scan", 5 | "Miss": "All you do is hurt people, they inflict 1-harm (ap) upon your subject.", 6 | "Hit": "**Hold 1, spend your hold 1 for 1 to ask a question**: \n* What was your character's lowest moment?\n* For what does your character crave forgivness, and of whom?\n* What are your character's secret pains?\n* In what ways are your character's mind and soul vulnerable?", 7 | "Crit": "**Hold 3, spend your hold 1 for 1 to ask a question**: \n* What was your character's lowest moment?\n* For what does your character crave forgivness, and of whom?\n* What are your character's secret pains?\n* In what ways are your character's mind and soul vulnerable?" 8 | }, 9 | 10 | { 11 | "Name": "!puppetstrings", 12 | "Full": "In-brain puppet strings", 13 | "Miss": "All you do is hurt people, they inflict 1-harm (ap) upon your subject.", 14 | "Hit": "**Hold 1. If they fullfill the command, all your holds are spent. Until then, At your will, no matter the circumstances, you can spend your hold 1 for 1**: \n* Inflict 1-harm (ap)\n* They take -1 right now", 15 | "Crit": "**Hold 3. If they fullfill the command, all your holds are spent. Until then, At your will, no matter the circumstances, you can spend your hold 1 for 1**: \n* Inflict 1-harm (ap)\n* They take -1 right now" 16 | }, 17 | ] 18 | -------------------------------------------------------------------------------- /data/game/apocalypse_world/brainer.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!deepbrain", 4 | "Full": "Deep Brain Scan", 5 | "Miss": "All you do is hurt people, they inflice 1-harm (ap) upon your subject.", 6 | "Hit": "**Hold 1, spend your hold 1 for 1 to ask a question**: \n* What was your character's lowest moment?\n* For what does your character crave forgivness, and of whom?\n* What are your character's secret pains?\n* In what ways are your character's mind and soul vulnerable?", 7 | "Crit": "**Hold 3, spend your hold 1 for 1 to ask a question**: \n* What was your character's lowest moment?\n* For what does your character crave forgivness, and of whom?\n* What are your character's secret pains?\n* In what ways are your character's mind and soul vulnerable?" 8 | }, 9 | 10 | { 11 | "Name": "!puppetstrings", 12 | "Full": "In-brain puppet strings", 13 | "Miss": "All you do is hurt people, they inflice 1-harm (ap) upon your subject.", 14 | "Hit": "**Hold 1. If they fullfill the command, all your holds are spent. Until then, At your will, no matter the circumstances, you can spend your hold 1 for 1**: \n* Inflict 1-harm (ap)\n* They take -1 right now", 15 | "Crit": "**Hold 3. If they fullfill the command, all your holds are spent. Until then, At your will, no matter the circumstances, you can spend your hold 1 for 1**: \n* Inflict 1-harm (ap)\n* They take -1 right now" 16 | }, 17 | ] -------------------------------------------------------------------------------- /playbooks/savvyhead.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!thingsspeak", 4 | "Full": "Things Speak", 5 | "Miss": "This is what happens when you play with the Psychich Maelstrom. It plays with your head. Open your brain, but you've already missed the roll", 6 | "Hit": "Ask the MC 1 question.\n* Who handled this last befor me?\n* Who made this?\n* What strong emotions have been most recently nearby this?\n* What words have been said most recently nearby this?\n* What has been done most recently with this, or to this?\n* What's wrong with this, and how might I fix it?", 7 | "Crit": "Ask the MC 3 questions.\n* Who handled this last befor me?\n* Who made this?\n* What strong emotions have been most recently nearby this?\n* What words have been said most recently nearby this?\n* What has been done most recently with this, or to this?\n* What's wrong with this, and how might I fix it?" 8 | }, 9 | 10 | { 11 | "Name": "!bonefeel", 12 | "Full": "Bonefeel", 13 | "Miss": "The MC holds 1 and can spend it to have you be there already, but somehow pinned, caught or trapped.", 14 | "Hit": "Hold 1. You or the MC can spend that to have you already be where you need to be with proper tools and knowledge without any clear explanation why. Take +1forward now", 15 | "Crit": "Hold 1. You or the MC can spend that to have you already be where you need to be with proper tools and knowledge without any clear explanation why. Take +1forward now." 16 | }, 17 | ] -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/mytoken/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## DiscordGo MyToken Example 4 | 5 | This example demonstrates how to utilize DiscordGo to login with an email and 6 | password then to print out the Authentication Token for that user's account. 7 | 8 | Everytime this application is run a new authentication token is generated 9 | for your account. Logging you in via email and password then creating a new 10 | token is a cpu/mem expensive task for Discord. Because of that, it is highly 11 | recommended to avoid doing this very often. Please only use this once to get a 12 | token for your use and then always just your token. 13 | 14 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) 15 | Discord chat channel for support.** 16 | 17 | ### Build 18 | 19 | This assumes you already have a working Go environment setup and that 20 | DiscordGo is correctly installed on your system. 21 | 22 | From within the mytoken example folder, run the below command to compile the 23 | example. 24 | 25 | ```sh 26 | go build 27 | ``` 28 | 29 | ### Usage 30 | 31 | You must authenticate using both Email and Password for an account. 32 | 33 | ``` 34 | ./mytoken --help 35 | Usage of ./mytoken: 36 | -e string 37 | Account Email 38 | -p string 39 | Account Password 40 | ``` 41 | 42 | The below example shows how to start the program using an Email and Password for 43 | authentication. 44 | 45 | ```sh 46 | ./mytoken -e youremail@here.com -p MySecretPassword 47 | ``` 48 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/airhorn/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## DiscordGo Airhorn Example 4 | 5 | This example demonstrates how to utilize DiscordGo to listen for an !airhorn 6 | command in a channel and then play a sound to that user's current voice channel. 7 | 8 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) 9 | Discord chat channel for support.** 10 | 11 | ### Build 12 | 13 | This assumes you already have a working Go environment setup and that 14 | DiscordGo is correctly installed on your system. 15 | 16 | From within the airhorn example folder, run the below command to compile the 17 | example. 18 | 19 | ```sh 20 | go build 21 | ``` 22 | 23 | ### Usage 24 | 25 | ``` 26 | Usage of ./airhorn: 27 | -t string 28 | Bot Token 29 | ``` 30 | 31 | The below example shows how to start the bot from the airhorn example folder. 32 | 33 | ```sh 34 | ./airhorn -t YOUR_BOT_TOKEN 35 | ``` 36 | 37 | ### Creating sounds 38 | 39 | Airhorn bot uses [DCA](https://github.com/bwmarrin/dca) files, which are 40 | pre-computed files that are easy to send to Discord. 41 | 42 | If you would like to create your own DCA files, please use: 43 | * [dca-rs](https://github.com/nstafie/dca-rs) 44 | 45 | See the below example of creating a DCA file from a WAV file. This also works 46 | with MP3, FLAC, and many other file formats. Of course, you will need to 47 | [install](https://github.com/nstafie/dca-rs#installation) dca-rs first :) 48 | 49 | ```sh 50 | ./dca-rs -i --raw > 51 | ``` 52 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Bruce Marriner 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of discordgo nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /static/css/main.css: -------------------------------------------------------------------------------- 1 | /* 2 | * 1. BASICS 3 | */ 4 | 5 | *{ 6 | box-sizing: border-box; 7 | } 8 | 9 | html{ 10 | background: #efefef; 11 | } 12 | 13 | body { 14 | font-family: Garamond, Baskerville, 'Baskerville Old Face', 'Hoefler Text', 'Times New Roman', serif; 15 | font-size: 1.1em; 16 | margin:0 auto; 17 | max-width: 50em; 18 | background-color: white; 19 | padding:1em; 20 | } 21 | /* 22 | *2. Type 23 | */ 24 | h1, h2, h3{ 25 | font-family: 'Cinzel', serif; 26 | } 27 | 28 | h1{ 29 | font-size: 3em; 30 | margin:0 0 0.5em 0; 31 | line-height: 1; 32 | } 33 | 34 | h2{ 35 | font-size: 1.5em; 36 | margin: 0 0 1em 0; 37 | line-height: 1; 38 | } 39 | 40 | p{ 41 | line-height: 1.5; 42 | margin: 0 0 1.5em 0 43 | } 44 | 45 | kbd{ 46 | border: 1px solid #bbc; 47 | background-color: #efefef; 48 | border-radius: 2px; 49 | padding:0 0.3em; 50 | line-height: inherit; 51 | color: #333; 52 | } 53 | 54 | a:link { 55 | color:rgb(232, 51, 53); 56 | } 57 | 58 | a:visited { 59 | color: #666; 60 | } 61 | 62 | a:hover, a:active, a:focus{ 63 | color: rgb(158, 35, 36); 64 | } 65 | 66 | /* 67 | * 3. 68 | */ 69 | 70 | a.btn{ 71 | padding: 0.5em 0.7em; 72 | 73 | background: rgb(232, 51, 53); 74 | border-radius: 5px; 75 | 76 | color: white; 77 | text-decoration: none; 78 | font-weight: bold; 79 | 80 | transition: background-color 0.5s; 81 | } 82 | 83 | a.btn:hover, a.btn:focus, a.btn:active{ 84 | background: rgb(158, 35, 36); 85 | 86 | } 87 | 88 | .btn svg{ 89 | display: inline-block; 90 | width:1em; 91 | height:1em; 92 | vertical-align: middle; 93 | fill: currentColor; 94 | } 95 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/oauth2_test.go: -------------------------------------------------------------------------------- 1 | package discordgo_test 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/bwmarrin/discordgo" 8 | ) 9 | 10 | func ExampleApplication() { 11 | 12 | // Authentication Token pulled from environment variable DG_TOKEN 13 | Token := os.Getenv("DG_TOKEN") 14 | if Token == "" { 15 | return 16 | } 17 | 18 | // Create a new Discordgo session 19 | dg, err := discordgo.New(Token) 20 | if err != nil { 21 | log.Println(err) 22 | return 23 | } 24 | 25 | // Create an new Application 26 | ap := &discordgo.Application{} 27 | ap.Name = "TestApp" 28 | ap.Description = "TestDesc" 29 | ap, err = dg.ApplicationCreate(ap) 30 | log.Printf("ApplicationCreate: err: %+v, app: %+v\n", err, ap) 31 | 32 | // Get a specific Application by it's ID 33 | ap, err = dg.Application(ap.ID) 34 | log.Printf("Application: err: %+v, app: %+v\n", err, ap) 35 | 36 | // Update an existing Application with new values 37 | ap.Description = "Whooooa" 38 | ap, err = dg.ApplicationUpdate(ap.ID, ap) 39 | log.Printf("ApplicationUpdate: err: %+v, app: %+v\n", err, ap) 40 | 41 | // create a new bot account for this application 42 | bot, err := dg.ApplicationBotCreate(ap.ID) 43 | log.Printf("BotCreate: err: %+v, bot: %+v\n", err, bot) 44 | 45 | // Get a list of all applications for the authenticated user 46 | apps, err := dg.Applications() 47 | log.Printf("Applications: err: %+v, apps : %+v\n", err, apps) 48 | for k, v := range apps { 49 | log.Printf("Applications: %d : %+v\n", k, v) 50 | } 51 | 52 | // Delete the application we created. 53 | err = dg.ApplicationDelete(ap.ID) 54 | log.Printf("Delete: err: %+v\n", err) 55 | 56 | return 57 | } 58 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/docs/index.md: -------------------------------------------------------------------------------- 1 | ## DiscordGo 2 |
3 | 4 | 5 | [Go](https://golang.org/) (golang) interface for the [Discord](https://discordapp.com/) 6 | chat service. Provides both low-level direct bindings to the 7 | Discord API and helper functions that allow you to make custom clients and chat 8 | bot applications easily. 9 | 10 | [Discord](https://discordapp.com/) is an all-in-one voice and text chat for 11 | gamers that's free, secure, and works on both your desktop and phone. 12 | 13 | ### Why DiscordGo? 14 | * High Performance 15 | * Minimal Memory & CPU Load 16 | * Low-level bindings to Discord REST API Endpoints 17 | * Support for the data websocket interface 18 | * Multi-Server voice connections (send and receive) 19 | * State tracking and caching 20 | 21 | ### Learn More 22 | * Check out the [Getting Started](GettingStarted) section 23 | * Read the reference docs on [Godoc](https://godoc.org/github.com/bwmarrin/discordgo) or [GoWalker](https://gowalker.org/github.com/bwmarrin/discordgo) 24 | * Try the [examples](https://github.com/bwmarrin/discordgo/tree/master/examples) 25 | * Explore [Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo) 26 | 27 | ### Join Us! 28 | Both of the below links take you to chat channels where you can get more 29 | information and support for DiscordGo. There's also a chance to make some 30 | friends :) 31 | 32 | * Join the [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) chat server dedicated to Go programming. 33 | * Join the [Discord API](https://discord.gg/0SBTUU1wZTWT6sqd) chat server dedicated to the Discord API. 34 | -------------------------------------------------------------------------------- /playbooks/angel.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!healingtouch", 4 | "Full": "Healing Touch", 5 | "Miss": "You heal nothing. Both you and the person you lied to about healing have to openbrain except you've already missed the roll", 6 | "Hit": "You put some guts back, heal 1 segment but you gotta openbrain now and hope the Pyschic Maelstrom plays nice.", 7 | "Crit": "You put lots of guts back. heal 1 segment." 8 | }, 9 | { 10 | "Name": "!kit", 11 | "Full": "Angel Kit - Stabalize at 9:00 or past", 12 | "Miss": "That shoudn't spurt like that. You deal 1-harm to your patient.", 13 | "Hit": "**They're okay, for now. Put them back to 6:00 and choose 1**:\n* They fight you and you have to narco stab them. How long will they be out?\n* The pain and drugs make them babble the truth to you. Ask them what secret they spill.\n* They respond well to the treatment. Recover 1 of the stock you spent, if you spent any.\n* They are at your complete mercy. What do you do to them?\n* Their course of recovery teaches you something about your craft. Mark experience.\n* They owe you for your time, attention, and supplies, and you're going to hold them to it. ", 14 | "Crit": "**They're okay, for now. Put them back to 6:00 and choose 2**:\n* They fight you and you have to narco stab them. How long will they be out?\n* The pain and drugs make them babble the truth to you. Ask them what secret they spill.\n* They respond well to the treatment. Recover 1 of the stock you spent, if you spent any.\n* They are at your complete mercy. What do you do to them?\n* Their course of recovery teaches you something about your craft. Mark experience.\n* They owe you for your time, attention, and supplies, and you're going to hold them to it." 15 | }, 16 | ] 17 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/appmaker/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## DiscordGo AppMaker Example 4 | 5 | This example demonstrates how to utilize DiscordGo to create, view, and delete 6 | Bot Applications on your account. 7 | 8 | These tasks are normally accomplished from the 9 | [Discord Developers](https://discordapp.com/developers/applications/me) site. 10 | 11 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) 12 | Discord chat channel for support.** 13 | 14 | ### Build 15 | 16 | This assumes you already have a working Go environment setup and that 17 | DiscordGo is correctly installed on your system. 18 | 19 | From within the appmaker example folder, run the below command to compile the 20 | example. 21 | 22 | ```sh 23 | go build 24 | ``` 25 | 26 | ### Usage 27 | 28 | This example only uses authentication tokens for authentication. While 29 | user email/password is supported by DiscordGo, it is not recommended. 30 | 31 | ``` 32 | ./appmaker --help 33 | Usage of ./appmaker: 34 | -d string 35 | Application ID to delete 36 | -l List Applications Only 37 | -n string 38 | Name to give App/Bot 39 | -t string 40 | Owner Account Token 41 | ``` 42 | 43 | * Account Token is required. The account will be the "owner" of any bot 44 | applications created. 45 | 46 | * If you provide the **-l** flag than appmaker will only display a list of 47 | applications on the provided account. 48 | 49 | * If you provide a **-d** flag with a valid application ID then that application 50 | will be deleted. 51 | 52 | Below example will create a new Bot Application under the given account. 53 | The Bot will be named **DiscordGoRocks** 54 | 55 | ```sh 56 | ./appmaker -t YOUR_USER_TOKEN -n DiscordGoRocks 57 | ``` 58 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/types.go: -------------------------------------------------------------------------------- 1 | // Discordgo - Discord bindings for Go 2 | // Available at https://github.com/bwmarrin/discordgo 3 | 4 | // Copyright 2015-2016 Bruce Marriner . All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | // This file contains custom types, currently only a timestamp wrapper. 9 | 10 | package discordgo 11 | 12 | import ( 13 | "encoding/json" 14 | "fmt" 15 | "net/http" 16 | "time" 17 | ) 18 | 19 | // Timestamp stores a timestamp, as sent by the Discord API. 20 | type Timestamp string 21 | 22 | // Parse parses a timestamp string into a time.Time object. 23 | // The only time this can fail is if Discord changes their timestamp format. 24 | func (t Timestamp) Parse() (time.Time, error) { 25 | return time.Parse(time.RFC3339, string(t)) 26 | } 27 | 28 | // RESTError stores error information about a request with a bad response code. 29 | // Message is not always present, there are cases where api calls can fail 30 | // without returning a json message. 31 | type RESTError struct { 32 | Request *http.Request 33 | Response *http.Response 34 | ResponseBody []byte 35 | 36 | Message *APIErrorMessage // Message may be nil. 37 | } 38 | 39 | func newRestError(req *http.Request, resp *http.Response, body []byte) *RESTError { 40 | restErr := &RESTError{ 41 | Request: req, 42 | Response: resp, 43 | ResponseBody: body, 44 | } 45 | 46 | // Attempt to decode the error and assume no message was provided if it fails 47 | var msg *APIErrorMessage 48 | err := json.Unmarshal(body, &msg) 49 | if err == nil { 50 | restErr.Message = msg 51 | } 52 | 53 | return restErr 54 | } 55 | 56 | func (r RESTError) Error() string { 57 | return fmt.Sprintf("HTTP %s, %s", r.Response.Status, r.ResponseBody) 58 | } 59 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/bwmarrin/discordgo" 11 | ) 12 | 13 | // Variables used for command line parameters 14 | var ( 15 | Token string 16 | ) 17 | 18 | func init() { 19 | 20 | flag.StringVar(&Token, "t", "", "Bot Token") 21 | flag.Parse() 22 | } 23 | 24 | func main() { 25 | 26 | // Create a new Discord session using the provided bot token. 27 | dg, err := discordgo.New("Bot " + Token) 28 | if err != nil { 29 | fmt.Println("error creating Discord session,", err) 30 | return 31 | } 32 | 33 | // Register the messageCreate func as a callback for MessageCreate events. 34 | dg.AddHandler(messageCreate) 35 | 36 | // Open a websocket connection to Discord and begin listening. 37 | err = dg.Open() 38 | if err != nil { 39 | fmt.Println("error opening connection,", err) 40 | return 41 | } 42 | 43 | // Wait here until CTRL-C or other term signal is received. 44 | fmt.Println("Bot is now running. Press CTRL-C to exit.") 45 | sc := make(chan os.Signal, 1) 46 | signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) 47 | <-sc 48 | 49 | // Cleanly close down the Discord session. 50 | dg.Close() 51 | } 52 | 53 | // This function will be called (due to AddHandler above) every time a new 54 | // message is created on any channel that the autenticated bot has access to. 55 | func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { 56 | 57 | // Ignore all messages created by the bot itself 58 | // This isn't required in this specific example but it's a good practice. 59 | if m.Author.ID == s.State.User.ID { 60 | return 61 | } 62 | // If the message is "ping" reply with "Pong!" 63 | if m.Content == "ping" { 64 | s.ChannelMessageSend(m.ChannelID, "Pong!") 65 | } 66 | 67 | // If the message is "pong" reply with "Ping!" 68 | if m.Content == "pong" { 69 | s.ChannelMessageSend(m.ChannelID, "Ping!") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /docs/register.md: -------------------------------------------------------------------------------- 1 | # Registering Apocalyptica 2 | > **IN PROGRESS! DO NOT USE!!!** Still testing out some things. 3 | 4 | ## Overview 5 | 6 | This document covers instructions for registering apps with Discord. Looking for information on how to deploy? Go to ['Deploying Apocalyptica'](deploying.md). 7 | 8 | Interested in test driving Apocalyptica before setting up your own? [Go to the Apocalyptica Reference install](https://apocalyptica.social-fiction.net) and invite it to your server. 9 | 10 | ## Steps to Register Apocalyptica in Discord. 11 | 12 | Sign in to Discord and go to the [Developer page, 'My Apps'](https://discordapp.com/developers/applications/me). 13 | ![Registering a New App](img/register_part_1.png) 14 | 15 | Select 'New App'. 16 | 17 | ![Registration Details](img/register_part_2.png) 18 | 19 | Give your instance of apocalyptica a name, description, and picture. 20 | 21 | ![Creating a Bot User](img/register_part_3.png) 22 | 23 | Select 'Creat a Bot User', then on the following screen: 24 | 25 | ![Retrieving ID, Client Information](img/register_part_4.png) 26 | 27 | Click 'click to reveal' and copy the token string. You will want to save this in your `env_discord.sample` file in the variable `DISCORD_TOKEN`. 28 | 29 | Above the 'Create a Bot User' information is the client ID of the application. Copy that ID into `env_discord.sample` file in the variable `CLIENT_ID`. 30 | 31 | When you have retrieved that information, save the changes. 32 | 33 | ![Save Changes](img/register_final.png) 34 | 35 | 36 | ## Invite Apocalyptica to a Server 37 | 38 | Now that you have an app registered, you need to invite it to your server. To do that you need an authorization link. 39 | 40 | The authorize link looks like this: 41 | `https://discordapp.com/oauth2/authorize?&client_id=&scope=bot&permissions=0` 42 | 43 | which will take you to a page like this: 44 | 45 | ![Authorize App](img/register_authorize.png) 46 | 47 | Choose one of your servers from the list, and choose 'Authorize' to invite Apocalyptica to the server! 48 | -------------------------------------------------------------------------------- /pkg/moves/moves.go: -------------------------------------------------------------------------------- 1 | package moves 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "math/rand" 10 | "os" 11 | "regexp" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | type roll interface { 17 | Roll() int 18 | Display() string 19 | } 20 | 21 | func die(dieType int) int { 22 | now := time.Now() 23 | //now.Unix() seems to return seconds, so all die rolls in the same second 24 | // would be seeded the same. UnixNano() returns a much more fine grained value 25 | rand.Seed(now.UnixNano()) 26 | //'Roll' the die, +1 since Intn is 0 based 27 | return (rand.Intn(dieType) + 1) 28 | } 29 | 30 | func (m Move) Roll(bonus int) int { 31 | return (die(6) + die(6) + bonus) 32 | } 33 | 34 | func (m Move) Display(r, bonus int) string { 35 | choice := "" 36 | switch { 37 | case r <= 6: 38 | choice = m[0].Miss 39 | case (r >= 7 && r <= 9): 40 | choice = m[0].Hit 41 | case (r >= 10): 42 | choice = m[0].Crit 43 | } 44 | 45 | return fmt.Sprintf("**%d**[%d *%d*] %s", r, (r - bonus), bonus, choice) 46 | } 47 | 48 | type Move []struct { 49 | Name string `json:"Name"` 50 | Full string `json:"Full"` 51 | Miss string `json:"Miss"` 52 | Hit string `json:"Hit"` 53 | Crit string `json:"Crit"` 54 | } 55 | 56 | // this is going to move over to 57 | func LoadMoves(filename string) Move { 58 | var mv Move 59 | file, e := ioutil.ReadFile(filename) 60 | if e != nil { 61 | log.Printf("File error: %v\n", e) 62 | os.Exit(1) 63 | } 64 | r := bytes.NewReader(file) 65 | if e := json.NewDecoder(r).Decode(&mv); e != nil { 66 | log.Printf("Problems decoding json: %v\n", e) 67 | os.Exit(1) 68 | } 69 | return mv 70 | } 71 | 72 | func FindMove(s string, mv Move) Move { 73 | var Found Move 74 | substr := strings.Split(s, " ") 75 | for _, v := range mv { 76 | r, err := regexp.Compile(v.Name) 77 | if err != nil { 78 | fmt.Printf("Problematic regexp, failing.\n") 79 | os.Exit(1) 80 | } 81 | if r.MatchString(substr[0]) == true { 82 | Found = append(Found, v) 83 | } 84 | } 85 | return Found 86 | } 87 | -------------------------------------------------------------------------------- /docs/deploying.md: -------------------------------------------------------------------------------- 1 | Deploying Apocalyptica 2 | --- 3 | > **IN PROGRESS! DO NOT USE!!!** Still testing out some things. 4 | 5 | > :memo: current version of the document is for Docker savvyheads! We will include many different deployment profiles soon. 6 | 7 | ## Docker (Quickstart & Custom) 8 | 9 | ### Requirements 10 | If you want to deploy your own instance of Apocalyptica, you will need: 11 | 12 | * A Discord Account and [a registered app](register.md). **Please register app before starting a deploy!** You need `DISCORD_TOKEN` and `CLIENT_ID` in your `.env` file to deploy successfully. 13 | * Docker (or other container runtime engine, though it has only been tested with Docker) 14 | * Instructions are written assuming OSX/Linux access, though it should be easy to adapt to Windows. 15 | * Web server needs access to port 8080. 16 | 17 | ### Docker Quickstart 18 | 19 | > :information_source: The Docker quickstart is when you want to get going quickly and only want/require the default games and playbooks installed. 20 | 21 | To build with defaults: 22 | 23 | * fill out `env_discord.sample` with `DISCORD_TOKEN` and `CLIENT_ID` values, then copy that file to `.env` under the root file of the repo. 24 | * from the root of the repo, run `bin/docker_quickstart`, which is really a wrapper script for: 25 | 26 | `docker run --name apocalyptica --rm --env-file -p8080:8080 ../.env -d gamefiend/apocalyptica` 27 | 28 | ### Docker Custom 29 | 30 | > :information_source: The Docker custom option is when you want to add your own custom games and playbooks to Apocalyptica. 31 | 32 | To build with custom information: 33 | 34 | * fill out `env_discord.sample` with `DISCORD_TOKEN` and `CLIENT_ID` values, then copy that file to `.env` under the root file of the repo. 35 | * load your custom games and playbooks into `data/games` 36 | * from the root of the repo, run `bin/docker_custom`, which is really a wrapper script for: 37 | 38 | ``` 39 | docker build -t apocalyptica-custom \ 40 | && docker run --name apocalyptica --rm --env-file -p8080:8080 ../.env -d apocalyptica-custom 41 | ``` 42 | 43 | This builds a custom container image that uses your custom info instead of using the generally available docker image. 44 | 45 | 46 | ## Deploying to Kubernetes 47 | 48 | > :memo: COMING SOON (REALLY SOON) 49 | 50 | 51 | ## Using Go Binary 52 | 53 | > :memo: COMING SOON 54 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/avatar/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | 11 | "github.com/bwmarrin/discordgo" 12 | ) 13 | 14 | // Variables used for command line parameters 15 | var ( 16 | Token string 17 | AvatarFile string 18 | AvatarURL string 19 | ) 20 | 21 | func init() { 22 | 23 | flag.StringVar(&Token, "t", "", "Bot Token") 24 | flag.StringVar(&AvatarFile, "f", "", "Avatar File Name") 25 | flag.StringVar(&AvatarURL, "u", "", "URL to the avatar image") 26 | flag.Parse() 27 | 28 | if Token == "" || (AvatarFile == "" && AvatarURL == "") { 29 | flag.Usage() 30 | os.Exit(1) 31 | } 32 | } 33 | 34 | func main() { 35 | 36 | // Create a new Discord session using the provided login information. 37 | dg, err := discordgo.New("Bot " + Token) 38 | if err != nil { 39 | fmt.Println("error creating Discord session,", err) 40 | return 41 | } 42 | 43 | // Declare these here so they can be used in the below two if blocks and 44 | // still carry over to the end of this function. 45 | var base64img string 46 | var contentType string 47 | 48 | // If we're using a URL link for the Avatar 49 | if AvatarURL != "" { 50 | 51 | resp, err := http.Get(AvatarURL) 52 | if err != nil { 53 | fmt.Println("Error retrieving the file, ", err) 54 | return 55 | } 56 | 57 | defer func() { 58 | _ = resp.Body.Close() 59 | }() 60 | 61 | img, err := ioutil.ReadAll(resp.Body) 62 | if err != nil { 63 | fmt.Println("Error reading the response, ", err) 64 | return 65 | } 66 | 67 | contentType = http.DetectContentType(img) 68 | base64img = base64.StdEncoding.EncodeToString(img) 69 | } 70 | 71 | // If we're using a local file for the Avatar 72 | if AvatarFile != "" { 73 | img, err := ioutil.ReadFile(AvatarFile) 74 | if err != nil { 75 | fmt.Println(err) 76 | } 77 | 78 | contentType = http.DetectContentType(img) 79 | base64img = base64.StdEncoding.EncodeToString(img) 80 | } 81 | 82 | // Now lets format our base64 image into the proper format Discord wants 83 | // and then call UserUpdate to set it as our user's Avatar. 84 | avatar := fmt.Sprintf("data:%s;base64,%s", contentType, base64img) 85 | _, err = dg.UserUpdate("", "", "", avatar, "") 86 | if err != nil { 87 | fmt.Println(err) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/appmaker/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/bwmarrin/discordgo" 10 | ) 11 | 12 | // Variables used for command line options 13 | var ( 14 | Token string 15 | Name string 16 | DeleteID string 17 | ListOnly bool 18 | ) 19 | 20 | func init() { 21 | 22 | flag.StringVar(&Token, "t", "", "Owner Account Token") 23 | flag.StringVar(&Name, "n", "", "Name to give App/Bot") 24 | flag.StringVar(&DeleteID, "d", "", "Application ID to delete") 25 | flag.BoolVar(&ListOnly, "l", false, "List Applications Only") 26 | flag.Parse() 27 | 28 | if Token == "" { 29 | flag.Usage() 30 | os.Exit(1) 31 | } 32 | } 33 | 34 | func main() { 35 | 36 | var err error 37 | 38 | // Create a new Discord session using the provided login information. 39 | dg, err := discordgo.New(Token) 40 | if err != nil { 41 | fmt.Println("error creating Discord session,", err) 42 | return 43 | } 44 | 45 | // If -l set, only display a list of existing applications 46 | // for the given account. 47 | if ListOnly { 48 | 49 | aps, err := dg.Applications() 50 | if err != nil { 51 | fmt.Println("error fetching applications,", err) 52 | return 53 | } 54 | 55 | for _, v := range aps { 56 | fmt.Println("-----------------------------------------------------") 57 | b, _ := json.MarshalIndent(v, "", " ") 58 | fmt.Println(string(b)) 59 | } 60 | return 61 | } 62 | 63 | // if -d set, delete the given Application 64 | if DeleteID != "" { 65 | err = dg.ApplicationDelete(DeleteID) 66 | if err != nil { 67 | fmt.Println("error deleting application,", err) 68 | } 69 | return 70 | } 71 | 72 | if Name == "" { 73 | flag.Usage() 74 | os.Exit(1) 75 | } 76 | 77 | // Create a new application. 78 | ap := &discordgo.Application{} 79 | ap.Name = Name 80 | ap, err = dg.ApplicationCreate(ap) 81 | if err != nil { 82 | fmt.Println("error creating new applicaiton,", err) 83 | return 84 | } 85 | 86 | fmt.Printf("Application created successfully:\n") 87 | b, _ := json.MarshalIndent(ap, "", " ") 88 | fmt.Println(string(b)) 89 | 90 | // Create the bot account under the application we just created 91 | bot, err := dg.ApplicationBotCreate(ap.ID) 92 | if err != nil { 93 | fmt.Println("error creating bot account,", err) 94 | return 95 | } 96 | 97 | fmt.Printf("Bot account created successfully.\n") 98 | b, _ = json.MarshalIndent(bot, "", " ") 99 | fmt.Println(string(b)) 100 | 101 | fmt.Println("Please save the above posted info in a secure place.") 102 | fmt.Println("You will need that information to login with your bot account.") 103 | } 104 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/logging.go: -------------------------------------------------------------------------------- 1 | // Discordgo - Discord bindings for Go 2 | // Available at https://github.com/bwmarrin/discordgo 3 | 4 | // Copyright 2015-2016 Bruce Marriner . All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | // This file contains code related to discordgo package logging 9 | 10 | package discordgo 11 | 12 | import ( 13 | "fmt" 14 | "log" 15 | "runtime" 16 | "strings" 17 | ) 18 | 19 | const ( 20 | 21 | // LogError level is used for critical errors that could lead to data loss 22 | // or panic that would not be returned to a calling function. 23 | LogError int = iota 24 | 25 | // LogWarning level is used for very abnormal events and errors that are 26 | // also returend to a calling function. 27 | LogWarning 28 | 29 | // LogInformational level is used for normal non-error activity 30 | LogInformational 31 | 32 | // LogDebug level is for very detailed non-error activity. This is 33 | // very spammy and will impact performance. 34 | LogDebug 35 | ) 36 | 37 | // msglog provides package wide logging consistancy for discordgo 38 | // the format, a... portion this command follows that of fmt.Printf 39 | // msgL : LogLevel of the message 40 | // caller : 1 + the number of callers away from the message source 41 | // format : Printf style message format 42 | // a ... : comma seperated list of values to pass 43 | func msglog(msgL, caller int, format string, a ...interface{}) { 44 | 45 | pc, file, line, _ := runtime.Caller(caller) 46 | 47 | files := strings.Split(file, "/") 48 | file = files[len(files)-1] 49 | 50 | name := runtime.FuncForPC(pc).Name() 51 | fns := strings.Split(name, ".") 52 | name = fns[len(fns)-1] 53 | 54 | msg := fmt.Sprintf(format, a...) 55 | 56 | log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg) 57 | } 58 | 59 | // helper function that wraps msglog for the Session struct 60 | // This adds a check to insure the message is only logged 61 | // if the session log level is equal or higher than the 62 | // message log level 63 | func (s *Session) log(msgL int, format string, a ...interface{}) { 64 | 65 | if msgL > s.LogLevel { 66 | return 67 | } 68 | 69 | msglog(msgL, 2, format, a...) 70 | } 71 | 72 | // helper function that wraps msglog for the VoiceConnection struct 73 | // This adds a check to insure the message is only logged 74 | // if the voice connection log level is equal or higher than the 75 | // message log level 76 | func (v *VoiceConnection) log(msgL int, format string, a ...interface{}) { 77 | 78 | if msgL > v.LogLevel { 79 | return 80 | } 81 | 82 | msglog(msgL, 2, format, a...) 83 | } 84 | 85 | // printJSON is a helper function to display JSON data in a easy to read format. 86 | /* NOT USED ATM 87 | func printJSON(body []byte) { 88 | var prettyJSON bytes.Buffer 89 | error := json.Indent(&prettyJSON, body, "", "\t") 90 | if error != nil { 91 | log.Print("JSON parse error: ", error) 92 | } 93 | log.Println(string(prettyJSON.Bytes())) 94 | } 95 | */ 96 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/template.go: -------------------------------------------------------------------------------- 1 | package moq 2 | 3 | // moqImports are the imports all moq files get. 4 | var moqImports = []string{} 5 | 6 | // moqTemplate is the template for mocked code. 7 | var moqTemplate = `// Code generated by moq; DO NOT EDIT 8 | // github.com/matryer/moq 9 | 10 | package {{.PackageName}} 11 | 12 | import ( 13 | {{- range .Imports }} 14 | "{{.}}" 15 | {{- end }} 16 | ) 17 | 18 | {{ range $i, $obj := .Objects -}} 19 | var ( 20 | {{- range .Methods }} 21 | lock{{$obj.InterfaceName}}Mock{{.Name}} sync.RWMutex 22 | {{- end }} 23 | ) 24 | 25 | // {{.InterfaceName}}Mock is a mock implementation of {{.InterfaceName}}. 26 | // 27 | // func TestSomethingThatUses{{.InterfaceName}}(t *testing.T) { 28 | // 29 | // // make and configure a mocked {{.InterfaceName}} 30 | // mocked{{.InterfaceName}} := &{{.InterfaceName}}Mock{ {{ range .Methods }} 31 | // {{.Name}}Func: func({{ .Arglist }}) {{.ReturnArglist}} { 32 | // panic("TODO: mock out the {{.Name}} method") 33 | // },{{- end }} 34 | // } 35 | // 36 | // // TODO: use mocked{{.InterfaceName}} in code that requires {{.InterfaceName}} 37 | // // and then make assertions. 38 | // 39 | // } 40 | type {{.InterfaceName}}Mock struct { 41 | {{- range .Methods }} 42 | // {{.Name}}Func mocks the {{.Name}} method. 43 | {{.Name}}Func func({{ .Arglist }}) {{.ReturnArglist}} 44 | {{ end }} 45 | // calls tracks calls to the methods. 46 | calls struct { 47 | {{- range .Methods }} 48 | // {{ .Name }} holds details about calls to the {{.Name}} method. 49 | {{ .Name }} []struct { 50 | {{- range .Params }} 51 | // {{ .Name | Exported }} is the {{ .Name }} argument value. 52 | {{ .Name | Exported }} {{ .Type }} 53 | {{- end }} 54 | } 55 | {{- end }} 56 | } 57 | } 58 | {{ range .Methods }} 59 | // {{.Name}} calls {{.Name}}Func. 60 | func (mock *{{$obj.InterfaceName}}Mock) {{.Name}}({{.Arglist}}) {{.ReturnArglist}} { 61 | if mock.{{.Name}}Func == nil { 62 | panic("moq: {{$obj.InterfaceName}}Mock.{{.Name}}Func is nil but {{$obj.InterfaceName}}.{{.Name}} was just called") 63 | } 64 | callInfo := struct { 65 | {{- range .Params }} 66 | {{ .Name | Exported }} {{ .Type }} 67 | {{- end }} 68 | }{ 69 | {{- range .Params }} 70 | {{ .Name | Exported }}: {{ .Name }}, 71 | {{- end }} 72 | } 73 | lock{{$obj.InterfaceName}}Mock{{.Name}}.Lock() 74 | mock.calls.{{.Name}} = append(mock.calls.{{.Name}}, callInfo) 75 | lock{{$obj.InterfaceName}}Mock{{.Name}}.Unlock() 76 | {{- if .ReturnArglist }} 77 | return mock.{{.Name}}Func({{.ArgCallList}}) 78 | {{- else }} 79 | mock.{{.Name}}Func({{.ArgCallList}}) 80 | {{- end }} 81 | } 82 | 83 | // {{.Name}}Calls gets all the calls that were made to {{.Name}}. 84 | // Check the length with: 85 | // len(mocked{{$obj.InterfaceName}}.{{.Name}}Calls()) 86 | func (mock *{{$obj.InterfaceName}}Mock) {{.Name}}Calls() []struct { 87 | {{- range .Params }} 88 | {{ .Name | Exported }} {{ .Type }} 89 | {{- end }} 90 | } { 91 | var calls []struct { 92 | {{- range .Params }} 93 | {{ .Name | Exported }} {{ .Type }} 94 | {{- end }} 95 | } 96 | lock{{$obj.InterfaceName}}Mock{{.Name}}.RLock() 97 | calls = mock.calls.{{.Name}} 98 | lock{{$obj.InterfaceName}}Mock{{.Name}}.RUnlock() 99 | return calls 100 | } 101 | {{ end -}} 102 | {{ end -}}` 103 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/ratelimit_test.go: -------------------------------------------------------------------------------- 1 | package discordgo 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | // This test takes ~2 seconds to run 11 | func TestRatelimitReset(t *testing.T) { 12 | rl := NewRatelimiter() 13 | 14 | sendReq := func(endpoint string) { 15 | bucket := rl.LockBucket(endpoint) 16 | 17 | headers := http.Header(make(map[string][]string)) 18 | 19 | headers.Set("X-RateLimit-Remaining", "0") 20 | // Reset for approx 2 seconds from now 21 | headers.Set("X-RateLimit-Reset", strconv.FormatInt(time.Now().Add(time.Second*2).Unix(), 10)) 22 | headers.Set("Date", time.Now().Format(time.RFC850)) 23 | 24 | err := bucket.Release(headers) 25 | if err != nil { 26 | t.Errorf("Release returned error: %v", err) 27 | } 28 | } 29 | 30 | sent := time.Now() 31 | sendReq("/guilds/99/channels") 32 | sendReq("/guilds/55/channels") 33 | sendReq("/guilds/66/channels") 34 | 35 | sendReq("/guilds/99/channels") 36 | sendReq("/guilds/55/channels") 37 | sendReq("/guilds/66/channels") 38 | 39 | // We hit the same endpoint 2 times, so we should only be ratelimited 2 second 40 | // And always less than 4 seconds (unless you're on a stoneage computer or using swap or something...) 41 | if time.Since(sent) >= time.Second && time.Since(sent) < time.Second*4 { 42 | t.Log("OK", time.Since(sent)) 43 | } else { 44 | t.Error("Did not ratelimit correctly, got:", time.Since(sent)) 45 | } 46 | } 47 | 48 | // This test takes ~1 seconds to run 49 | func TestRatelimitGlobal(t *testing.T) { 50 | rl := NewRatelimiter() 51 | 52 | sendReq := func(endpoint string) { 53 | bucket := rl.LockBucket(endpoint) 54 | 55 | headers := http.Header(make(map[string][]string)) 56 | 57 | headers.Set("X-RateLimit-Global", "1") 58 | // Reset for approx 1 seconds from now 59 | headers.Set("Retry-After", "1000") 60 | 61 | err := bucket.Release(headers) 62 | if err != nil { 63 | t.Errorf("Release returned error: %v", err) 64 | } 65 | } 66 | 67 | sent := time.Now() 68 | 69 | // This should trigger a global ratelimit 70 | sendReq("/guilds/99/channels") 71 | time.Sleep(time.Millisecond * 100) 72 | 73 | // This shouldn't go through in less than 1 second 74 | sendReq("/guilds/55/channels") 75 | 76 | if time.Since(sent) >= time.Second && time.Since(sent) < time.Second*2 { 77 | t.Log("OK", time.Since(sent)) 78 | } else { 79 | t.Error("Did not ratelimit correctly, got:", time.Since(sent)) 80 | } 81 | } 82 | 83 | func BenchmarkRatelimitSingleEndpoint(b *testing.B) { 84 | rl := NewRatelimiter() 85 | for i := 0; i < b.N; i++ { 86 | sendBenchReq("/guilds/99/channels", rl) 87 | } 88 | } 89 | 90 | func BenchmarkRatelimitParallelMultiEndpoints(b *testing.B) { 91 | rl := NewRatelimiter() 92 | b.RunParallel(func(pb *testing.PB) { 93 | i := 0 94 | for pb.Next() { 95 | sendBenchReq("/guilds/"+strconv.Itoa(i)+"/channels", rl) 96 | i++ 97 | } 98 | }) 99 | } 100 | 101 | // Does not actually send requests, but locks the bucket and releases it with made-up headers 102 | func sendBenchReq(endpoint string, rl *RateLimiter) { 103 | bucket := rl.LockBucket(endpoint) 104 | 105 | headers := http.Header(make(map[string][]string)) 106 | 107 | headers.Set("X-RateLimit-Remaining", "10") 108 | headers.Set("X-RateLimit-Reset", strconv.FormatInt(time.Now().Unix(), 10)) 109 | headers.Set("Date", time.Now().Format(time.RFC850)) 110 | 111 | bucket.Release(headers) 112 | } 113 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/README.md: -------------------------------------------------------------------------------- 1 | ![moq logo](moq-logo-small.png) [![Build Status](https://travis-ci.org/matryer/moq.svg?branch=master)](https://travis-ci.org/matryer/moq) [![Go Report Card](https://goreportcard.com/badge/github.com/matryer/moq)](https://goreportcard.com/report/github.com/matryer/moq) 2 | 3 | Interface mocking tool for go generate. 4 | 5 | By [Mat Ryer](https://twitter.com/matryer) and [David Hernandez](https://github.com/dahernan), with ideas lovingly stolen from [Ernesto Jimenez](https://github.com/ernesto-jimenez). 6 | 7 | ### What is Moq? 8 | 9 | Moq is a tool that generates a struct from any interface. The struct can be used in test code as a mock of the interface. 10 | 11 | ![Preview](preview.png) 12 | 13 | above: Moq generates the code on the right. 14 | 15 | You can read more in the [Meet Moq blog post](http://bit.ly/meetmoq). 16 | 17 | ### Installing 18 | 19 | To start using Moq, just run go get: 20 | ``` 21 | $ go get github.com/matryer/moq 22 | ``` 23 | 24 | ### Usage 25 | 26 | ``` 27 | moq [flags] destination interface [interface2 [interface3 [...]]] 28 | -out string 29 | output file (default stdout) 30 | -pkg string 31 | package name (default will infer) 32 | ``` 33 | 34 | In a command line: 35 | 36 | ``` 37 | $ moq -out mocks_test.go . MyInterface 38 | ``` 39 | 40 | In code (for go generate): 41 | 42 | ```go 43 | package my 44 | 45 | //go:generate moq -out myinterface_moq_test.go . MyInterface 46 | 47 | type MyInterface interface { 48 | Method1() error 49 | Method2(i int) 50 | } 51 | ``` 52 | 53 | Then run `go generate` for your package. 54 | 55 | ### How to use it 56 | 57 | Mocking interfaces is a nice way to write unit tests where you can easily control the behaviour of the mocked object. 58 | 59 | Moq creates a struct that has a function field for each method, which you can declare in your test code. 60 | 61 | This this example, Moq generated the `EmailSenderMock` type: 62 | 63 | ```go 64 | func TestCompleteSignup(t *testing.T) { 65 | 66 | var sentTo string 67 | 68 | mockedEmailSender = &EmailSenderMock{ 69 | SendFunc: func(to, subject, body string) error { 70 | sentTo = to 71 | return nil 72 | }, 73 | } 74 | 75 | CompleteSignUp("me@email.com", mockedEmailSender) 76 | 77 | callsToSend := len(mockedEmailSender.SendCalls()) 78 | if callsToSend != 1 { 79 | t.Errorf("Send was called %d times", callsToSend) 80 | } 81 | if sentTo != "me@email.com" { 82 | t.Errorf("unexpected recipient: %s", sentTo) 83 | } 84 | 85 | } 86 | 87 | func CompleteSignUp(to string, sender EmailSender) { 88 | // TODO: this 89 | } 90 | ``` 91 | 92 | The mocked structure implements the interface, where each method calls the associated function field. 93 | 94 | ## Tips 95 | 96 | * Keep mocked logic inside the test that is using it 97 | * Only mock the fields you need 98 | * It will panic if a nil function gets called 99 | * Name arguments in the interface for a better experience 100 | * Use closured variables inside your test function to capture details about the calls to the methods 101 | * Use `.MethodCalls()` to track the calls 102 | * Use `go:generate` to invoke the `moq` command 103 | 104 | ## License 105 | 106 | The Moq project (and all code) is licensed under the [MIT License](LICENSE). 107 | 108 | The Moq logo was created by [Chris Ryer](http://chrisryer.co.uk) and is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/). 109 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/tools/cmd/eventhandlers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "go/format" 6 | "go/parser" 7 | "go/token" 8 | "io/ioutil" 9 | "log" 10 | "path/filepath" 11 | "regexp" 12 | "sort" 13 | "strings" 14 | "text/template" 15 | ) 16 | 17 | var eventHandlerTmpl = template.Must(template.New("eventHandler").Funcs(template.FuncMap{ 18 | "constName": constName, 19 | "isDiscordEvent": isDiscordEvent, 20 | "privateName": privateName, 21 | }).Parse(`// Code generated by \"eventhandlers\"; DO NOT EDIT 22 | // See events.go 23 | 24 | package discordgo 25 | 26 | // Following are all the event types. 27 | // Event type values are used to match the events returned by Discord. 28 | // EventTypes surrounded by __ are synthetic and are internal to DiscordGo. 29 | const ({{range .}} 30 | {{privateName .}}EventType = "{{constName .}}"{{end}} 31 | ) 32 | {{range .}} 33 | // {{privateName .}}EventHandler is an event handler for {{.}} events. 34 | type {{privateName .}}EventHandler func(*Session, *{{.}}) 35 | 36 | // Type returns the event type for {{.}} events. 37 | func (eh {{privateName .}}EventHandler) Type() string { 38 | return {{privateName .}}EventType 39 | } 40 | {{if isDiscordEvent .}} 41 | // New returns a new instance of {{.}}. 42 | func (eh {{privateName .}}EventHandler) New() interface{} { 43 | return &{{.}}{} 44 | }{{end}} 45 | // Handle is the handler for {{.}} events. 46 | func (eh {{privateName .}}EventHandler) Handle(s *Session, i interface{}) { 47 | if t, ok := i.(*{{.}}); ok { 48 | eh(s, t) 49 | } 50 | } 51 | 52 | {{end}} 53 | func handlerForInterface(handler interface{}) EventHandler { 54 | switch v := handler.(type) { 55 | case func(*Session, interface{}): 56 | return interfaceEventHandler(v){{range .}} 57 | case func(*Session, *{{.}}): 58 | return {{privateName .}}EventHandler(v){{end}} 59 | } 60 | 61 | return nil 62 | } 63 | 64 | func init() { {{range .}}{{if isDiscordEvent .}} 65 | registerInterfaceProvider({{privateName .}}EventHandler(nil)){{end}}{{end}} 66 | } 67 | `)) 68 | 69 | func main() { 70 | var buf bytes.Buffer 71 | dir := filepath.Dir(".") 72 | 73 | fs := token.NewFileSet() 74 | parsedFile, err := parser.ParseFile(fs, "events.go", nil, 0) 75 | if err != nil { 76 | log.Fatalf("warning: internal error: could not parse events.go: %s", err) 77 | return 78 | } 79 | 80 | names := []string{} 81 | for object := range parsedFile.Scope.Objects { 82 | names = append(names, object) 83 | } 84 | sort.Strings(names) 85 | eventHandlerTmpl.Execute(&buf, names) 86 | 87 | src, err := format.Source(buf.Bytes()) 88 | if err != nil { 89 | log.Println("warning: internal error: invalid Go generated:", err) 90 | src = buf.Bytes() 91 | } 92 | 93 | err = ioutil.WriteFile(filepath.Join(dir, strings.ToLower("eventhandlers.go")), src, 0644) 94 | if err != nil { 95 | log.Fatal(buf, "writing output: %s", err) 96 | } 97 | } 98 | 99 | var constRegexp = regexp.MustCompile("([a-z])([A-Z])") 100 | 101 | func constCase(name string) string { 102 | return strings.ToUpper(constRegexp.ReplaceAllString(name, "${1}_${2}")) 103 | } 104 | 105 | func isDiscordEvent(name string) bool { 106 | switch { 107 | case name == "Connect", name == "Disconnect", name == "Event", name == "RateLimit", name == "Interface": 108 | return false 109 | default: 110 | return true 111 | } 112 | } 113 | 114 | func constName(name string) string { 115 | if !isDiscordEvent(name) { 116 | return "__" + constCase(name) + "__" 117 | } 118 | 119 | return constCase(name) 120 | } 121 | 122 | func privateName(name string) string { 123 | return strings.ToLower(string(name[0])) + name[1:] 124 | } 125 | -------------------------------------------------------------------------------- /pkg/game/game.go: -------------------------------------------------------------------------------- 1 | // game manages data structures for games and playbooks arranged in format listed in docs/gamebook-playbook.md 2 | package game 3 | 4 | import ( 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/gamefiend/apocalyptica/pkg/moves" 9 | "github.com/gamefiend/apocalyptica/pkg/playbook" 10 | "io/ioutil" 11 | "log" 12 | "os" 13 | "runtime" 14 | "strings" 15 | ) 16 | 17 | var ( 18 | _, b, _, _ = runtime.Caller(0) 19 | basepath = filepath.Dir(b) 20 | gamepath = fmt.Sprintf("%s/data/game", basepath) 21 | ) 22 | 23 | //Gamebooks interface defines methods to initialize data structs from file structure. 24 | type Gamebooks interface { 25 | NewGame() (g Games) 26 | ListGamesAvailable() (l string) 27 | LoadMovesFromGame(dir string) (m Move) 28 | LoadMovesFromPlaybook(path string) (m Move) 29 | GetCurrentGame(f string) (l string) 30 | SetCurrentGame(f string) bool 31 | } 32 | 33 | //Games type is where `games.json` structure is unmarshalled into. 34 | type Games []struct { 35 | Name string `json:"Name"` 36 | Author string `json:"Author"` 37 | Description string `json:"Description"` 38 | Playbooks map[string]playbook `json:"Playboooks"` 39 | isSelected bool // NewGame sets default to false 40 | } 41 | 42 | //playbook type holds moves from a games' playbooks. 43 | type playbook struct { 44 | Name string `json:"Name"` 45 | File string `json:"Moves"` 46 | } 47 | 48 | func (g *Games) NewGame() (gg Games) { 49 | var gg Games 50 | files, e := ioutil.ReadDir(gamepath) 51 | for _, x := range files { 52 | if x == "game.json" { 53 | f, e := ioutil.ReadFile(x) 54 | if e != nil { 55 | log.Fatalf("Cannot open file %s : %v\n", x, e) 56 | } 57 | r := bytes.NewReader(f) 58 | if e := json.NewDecoder(r).Decode(&g); e != nil { 59 | log.Fatalf("Cannot decode json: %v\n", e) 60 | } 61 | } 62 | } 63 | return 64 | 65 | } 66 | 67 | func (g *Games) ListGamesAvailable() (l string) { 68 | gl := make([]string, 0, len(g)) 69 | for _, game := range g { 70 | output := fmt.Sprintf("**%s by %s**\n*%s*", game.Name, game.Author, game.Description) 71 | gl = append(gl, output) 72 | } 73 | return strings.Join(gl, "\n") 74 | } 75 | 76 | func (g *Games) LoadMovesFromGame(dir string) (m Move) { 77 | var m Move 78 | for _, p := range g { 79 | if p.isSelected { 80 | gpath := fmt.Sprintf("%s/%s", gamepath, dir) 81 | for _, x := range p.Playbooks { 82 | ppath := fmt.Sprintf("%s/%s", gpath, x.File) 83 | var pb Move 84 | r := ioutil.NewReader(ppath) 85 | if e := json.NewDecoder(r).Decode(&pb); e != nil { 86 | log.Fatalf("Cannot read playbook %s : %v\n", ppath, e) 87 | } 88 | m = append(m, pb) 89 | } 90 | 91 | } 92 | } 93 | return 94 | } 95 | 96 | func (g *Games) LoadMovesFromPlaybook(path string) (m Move) { 97 | var m Move 98 | f, e := ioutil.ReadFile(filepath) 99 | if e != nil { 100 | log.Fatalf("Cannot read file %s : %v\n", filepath, e) 101 | } 102 | r := bytes.NewReader(f) 103 | if e := json.NewDecoder(r).Decode(&m); e != nil { 104 | log.Fatalf("Problem unmarshalling json : %v", e) 105 | } 106 | return 107 | } 108 | 109 | func (g *Games) GetCurrentGame() string { 110 | for _, x := range g { 111 | if x.isSelected { 112 | return x.Name 113 | } 114 | } 115 | //if we have arrived here, we obviously have nothing selected... 116 | return "NoGameSelected" 117 | } 118 | 119 | func (g *Games) SetCurrentGame(f string) bool { 120 | for _, x := range g { 121 | if x.Name == f { 122 | if x.isSelected { 123 | return true 124 | } 125 | x.isSelected = true 126 | return true 127 | } 128 | x.isSelected = false 129 | } 130 | return false 131 | } 132 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/example/mockpersonstore_test.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | // AUTOGENERATED BY MOQ - DO NOT EDIT 4 | // github.com/matryer/moq 5 | 6 | import ( 7 | "context" 8 | "sync" 9 | ) 10 | 11 | var ( 12 | lockPersonStoreMockCreate sync.RWMutex 13 | lockPersonStoreMockGet sync.RWMutex 14 | ) 15 | 16 | // PersonStoreMock is a mock implementation of PersonStore. 17 | // 18 | // func TestSomethingThatUsesPersonStore(t *testing.T) { 19 | // 20 | // // make and configure a mocked PersonStore 21 | // mockedPersonStore := &PersonStoreMock{ 22 | // CreateFunc: func(ctx context.Context, person *Person, confirm bool) error { 23 | // panic("TODO: mock out the Create method") 24 | // }, 25 | // GetFunc: func(ctx context.Context, id string) (*Person, error) { 26 | // panic("TODO: mock out the Get method") 27 | // }, 28 | // } 29 | // 30 | // // TODO: use mockedPersonStore in code that requires PersonStore 31 | // // and then make assertions. 32 | // 33 | // } 34 | type PersonStoreMock struct { 35 | // CreateFunc mocks the Create method. 36 | CreateFunc func(ctx context.Context, person *Person, confirm bool) error 37 | 38 | // GetFunc mocks the Get method. 39 | GetFunc func(ctx context.Context, id string) (*Person, error) 40 | 41 | // calls tracks calls to the methods. 42 | calls struct { 43 | // Create holds details about calls to the Create method. 44 | Create []struct { 45 | // Ctx is the ctx argument value. 46 | Ctx context.Context 47 | // Person is the person argument value. 48 | Person *Person 49 | // Confirm is the confirm argument value. 50 | Confirm bool 51 | } 52 | // Get holds details about calls to the Get method. 53 | Get []struct { 54 | // Ctx is the ctx argument value. 55 | Ctx context.Context 56 | // Id is the id argument value. 57 | Id string 58 | } 59 | } 60 | } 61 | 62 | // Create calls CreateFunc. 63 | func (mock *PersonStoreMock) Create(ctx context.Context, person *Person, confirm bool) error { 64 | if mock.CreateFunc == nil { 65 | panic("moq: PersonStoreMock.CreateFunc is nil but PersonStore.Create was just called") 66 | } 67 | callInfo := struct { 68 | Ctx context.Context 69 | Person *Person 70 | Confirm bool 71 | }{ 72 | Ctx: ctx, 73 | Person: person, 74 | Confirm: confirm, 75 | } 76 | lockPersonStoreMockCreate.Lock() 77 | mock.calls.Create = append(mock.calls.Create, callInfo) 78 | lockPersonStoreMockCreate.Unlock() 79 | return mock.CreateFunc(ctx, person, confirm) 80 | } 81 | 82 | // CreateCalls gets all the calls that were made to Create. 83 | // Check the length with: 84 | // len(mockedPersonStore.CreateCalls()) 85 | func (mock *PersonStoreMock) CreateCalls() []struct { 86 | Ctx context.Context 87 | Person *Person 88 | Confirm bool 89 | } { 90 | var calls []struct { 91 | Ctx context.Context 92 | Person *Person 93 | Confirm bool 94 | } 95 | lockPersonStoreMockCreate.RLock() 96 | calls = mock.calls.Create 97 | lockPersonStoreMockCreate.RUnlock() 98 | return calls 99 | } 100 | 101 | // Get calls GetFunc. 102 | func (mock *PersonStoreMock) Get(ctx context.Context, id string) (*Person, error) { 103 | if mock.GetFunc == nil { 104 | panic("moq: PersonStoreMock.GetFunc is nil but PersonStore.Get was just called") 105 | } 106 | callInfo := struct { 107 | Ctx context.Context 108 | Id string 109 | }{ 110 | Ctx: ctx, 111 | Id: id, 112 | } 113 | lockPersonStoreMockGet.Lock() 114 | mock.calls.Get = append(mock.calls.Get, callInfo) 115 | lockPersonStoreMockGet.Unlock() 116 | return mock.GetFunc(ctx, id) 117 | } 118 | 119 | // GetCalls gets all the calls that were made to Get. 120 | // Check the length with: 121 | // len(mockedPersonStore.GetCalls()) 122 | func (mock *PersonStoreMock) GetCalls() []struct { 123 | Ctx context.Context 124 | Id string 125 | } { 126 | var calls []struct { 127 | Ctx context.Context 128 | Id string 129 | } 130 | lockPersonStoreMockGet.RLock() 131 | calls = mock.calls.Get 132 | lockPersonStoreMockGet.RUnlock() 133 | return calls 134 | } 135 | -------------------------------------------------------------------------------- /data/game/fellowship/basic.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!finish", 4 | "Full": "Finish them! (any stat)", 5 | "Miss": "You lose Advantage and **face retaliation**.", 6 | "Hit": "You deal damage to a stat that makes sense & lose Advantage.\n**If an ally was Keeping Them Busy**, they aren't anymore.", 7 | "Crit": "You **destroy them** - tell us what that means." 8 | }, 9 | { 10 | "Name": "!overcome", 11 | "Full": "Overcome! (Blood)", 12 | "Miss": "You fail to overcome the harm & **the harm happens** unimpeded", 13 | "Hit": "You can **Pay a Price** for full success,\nor you can **create a temporary solution** which delays the threat.", 14 | "Crit": "You **stop the obstacle**, threat, cut, or Move from causing harm." 15 | }, 16 | { 17 | "Name": "!keepbusy", 18 | "Full": "Keep them busy! (Courage)", 19 | "Miss": "Keeping them busy is a bust!", 20 | "Hit": "You can only stall them for a **short time**, and they will **retaliate against you** when time is up.\n**While you Keep Them Busy**, you create an Advantage for someone else.\nMark ammo if using a weapon with Ammo", 21 | "Crit": "You buy **as much time as you need**, and their attention is all on you.\n**While you Keep Them Busy**, you create an Advantage for someone else.\nMark ammo if using a weapon with Ammo" 22 | }, 23 | { 24 | "Name": "!getaway", 25 | "Full": "Get away! (Grace)", 26 | "Miss": "Getting away was a bust!", 27 | "Hit": "**Choose one:**\n* You get there quickly, avoiding any harm along the way.\n* You get there quietly, drawing no attention.\n* You grab someone nearby and bring them along with you.", 28 | "Crit": "**Choose two:**\n* You get there quickly, avoiding any harm along the way.\n* You get there quietly, drawing no attention.\n* You grab someone nearby and bring them along with you." 29 | }, 30 | { 31 | "Name": "!talksense", 32 | "Full": "Talk sense! (Grace/Sense/Wisdom)", 33 | "Miss": "**They choose one**:\n* The favor you must do is expensive, difficult, or demeaning.\n* They cannot do what you ask of them, and will tell you why.", 34 | "Hit": "**They do as you ask**, to the best of their ability.\nHowever, you **owe them a favor**.\n The more you ask of them, the more they'll ask of you, and they can cash in **at any time**.", 35 | "Crit": "**They do as you ask** to the best of their ability, no favor required." 36 | }, 37 | { 38 | "Name": "!lookclosely", 39 | "Full": "Look closely! (Sense)", 40 | "Miss": "**Ask one** (Anyone can answer, Overlord has final say):\n* What is going on here? What do my senses tell me?\n* Is something hidden or out of place? If so, what looks suspicious?\n* Tell me about ____. How could it hurt me? How could it help me?\n* Tell me about ____. What are they doing? What will they do next?\n* What will happen if I ____?", 41 | "Hit": "**Ask two** (Anyone can answer, Overlord has final say):\n* What is going on here? What do my senses tell me?\n* Is something hidden or out of place? If so, what looks suspicious?\n* Tell me about ____. How could it hurt me? How could it help me?\n* Tell me about ____. What are they doing? What will they do next?\n* What will happen if I ____?", 42 | "Crit": "**Ask three** (Anyone can answer, Overlord has final say):\n* What is going on here? What do my senses tell me?\n* Is something hidden or out of place? If so, what looks suspicious?\n* Tell me about ____. How could it hurt me? How could it help me?\n* Tell me about ____. What are they doing? What will they do next?\n* What will happen if I ____?" 43 | }, 44 | { 45 | "Name": "!speaksoftly", 46 | "Full": "Speak softly! (Wisdom)", 47 | "Miss": "**Ask one**:\n* What can they tell us about ____?\n* What were tehy doing, and what are they going to do next?\n* What should I be wary of when dealing with them? What do they want, and how could we help them get it?\n* What would they have us do next?", 48 | "Hit": "**Ask two**:\n* What can they tell us about ____?\n* What were tehy doing, and what are they going to do next?\n* What should I be wary of when dealing with them? What do they want, and how could we help them get it?\n* What would they have us do next?", 49 | "Crit": "**Ask three**:\n* What can they tell us about ____?\n* What were tehy doing, and what are they going to do next?\n* What should I be wary of when dealing with them? What do they want, and how could we help them get it?\n* What would they have us do next?" 50 | } 51 | ] 52 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/oauth2.go: -------------------------------------------------------------------------------- 1 | // Discordgo - Discord bindings for Go 2 | // Available at https://github.com/bwmarrin/discordgo 3 | 4 | // Copyright 2015-2016 Bruce Marriner . All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | // This file contains functions related to Discord OAuth2 endpoints 9 | 10 | package discordgo 11 | 12 | // ------------------------------------------------------------------------------------------------ 13 | // Code specific to Discord OAuth2 Applications 14 | // ------------------------------------------------------------------------------------------------ 15 | 16 | // An Application struct stores values for a Discord OAuth2 Application 17 | type Application struct { 18 | ID string `json:"id,omitempty"` 19 | Name string `json:"name"` 20 | Description string `json:"description,omitempty"` 21 | Icon string `json:"icon,omitempty"` 22 | Secret string `json:"secret,omitempty"` 23 | RedirectURIs *[]string `json:"redirect_uris,omitempty"` 24 | BotRequireCodeGrant bool `json:"bot_require_code_grant,omitempty"` 25 | BotPublic bool `json:"bot_public,omitempty"` 26 | RPCApplicationState int `json:"rpc_application_state,omitempty"` 27 | Flags int `json:"flags,omitempty"` 28 | Owner *User `json:"owner"` 29 | Bot *User `json:"bot"` 30 | } 31 | 32 | // Application returns an Application structure of a specific Application 33 | // appID : The ID of an Application 34 | func (s *Session) Application(appID string) (st *Application, err error) { 35 | 36 | body, err := s.RequestWithBucketID("GET", EndpointApplication(appID), nil, EndpointApplication("")) 37 | if err != nil { 38 | return 39 | } 40 | 41 | err = unmarshal(body, &st) 42 | return 43 | } 44 | 45 | // Applications returns all applications for the authenticated user 46 | func (s *Session) Applications() (st []*Application, err error) { 47 | 48 | body, err := s.RequestWithBucketID("GET", EndpointApplications, nil, EndpointApplications) 49 | if err != nil { 50 | return 51 | } 52 | 53 | err = unmarshal(body, &st) 54 | return 55 | } 56 | 57 | // ApplicationCreate creates a new Application 58 | // name : Name of Application / Bot 59 | // uris : Redirect URIs (Not required) 60 | func (s *Session) ApplicationCreate(ap *Application) (st *Application, err error) { 61 | 62 | data := struct { 63 | Name string `json:"name"` 64 | Description string `json:"description"` 65 | RedirectURIs *[]string `json:"redirect_uris,omitempty"` 66 | }{ap.Name, ap.Description, ap.RedirectURIs} 67 | 68 | body, err := s.RequestWithBucketID("POST", EndpointApplications, data, EndpointApplications) 69 | if err != nil { 70 | return 71 | } 72 | 73 | err = unmarshal(body, &st) 74 | return 75 | } 76 | 77 | // ApplicationUpdate updates an existing Application 78 | // var : desc 79 | func (s *Session) ApplicationUpdate(appID string, ap *Application) (st *Application, err error) { 80 | 81 | data := struct { 82 | Name string `json:"name"` 83 | Description string `json:"description"` 84 | RedirectURIs *[]string `json:"redirect_uris,omitempty"` 85 | }{ap.Name, ap.Description, ap.RedirectURIs} 86 | 87 | body, err := s.RequestWithBucketID("PUT", EndpointApplication(appID), data, EndpointApplication("")) 88 | if err != nil { 89 | return 90 | } 91 | 92 | err = unmarshal(body, &st) 93 | return 94 | } 95 | 96 | // ApplicationDelete deletes an existing Application 97 | // appID : The ID of an Application 98 | func (s *Session) ApplicationDelete(appID string) (err error) { 99 | 100 | _, err = s.RequestWithBucketID("DELETE", EndpointApplication(appID), nil, EndpointApplication("")) 101 | if err != nil { 102 | return 103 | } 104 | 105 | return 106 | } 107 | 108 | // ------------------------------------------------------------------------------------------------ 109 | // Code specific to Discord OAuth2 Application Bots 110 | // ------------------------------------------------------------------------------------------------ 111 | 112 | // ApplicationBotCreate creates an Application Bot Account 113 | // 114 | // appID : The ID of an Application 115 | // 116 | // NOTE: func name may change, if I can think up something better. 117 | func (s *Session) ApplicationBotCreate(appID string) (st *User, err error) { 118 | 119 | body, err := s.RequestWithBucketID("POST", EndpointApplicationsBot(appID), nil, EndpointApplicationsBot("")) 120 | if err != nil { 121 | return 122 | } 123 | 124 | err = unmarshal(body, &st) 125 | return 126 | } 127 | -------------------------------------------------------------------------------- /data/game/apocalypse_world/basic.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "!goaggro", 4 | "Full": "Go Aggro on Someone", 5 | "Miss": "Going aggro is a bust!", 6 | "Hit": "Your opponent chooses 1:\n* Get the hell out of your way.\n* Barricade themselves securely in.\n* Give you something they think you want.\n* Back off calmly, hands where you can see.\n* Tell you what you want to know (or what you want to hear).", 7 | "Crit": "Your opponent can either: force your hand and suck it up, or cave and do what you want." 8 | }, 9 | 10 | { 11 | "Name": "!readsitch", 12 | "Full": "Read a Sitch", 13 | "Miss": "You can't get a read on what's going on...", 14 | "Hit": "**Ask the MC 1 of these questions**:\n* Where’s my best escape route / way in / way past?\n* Which enemy is most vulnerable to me?\n* Which enemy is the biggest threat?\n* What should I be on the lookout for?\n* What’s my enemy’s true position?\n* Who’s in control here?", 15 | "Crit": "**Ask the MC 3 of these questions**:\n* Where’s my best escape route / way in / way past?\n* Which enemy is most vulnerable to me?\n* Which enemy is the biggest threat?\n* What should I be on the lookout for?\n* What’s my enemy’s true position?\n* Who’s in control here?" 16 | }, 17 | { 18 | "Name": "!openbrain", 19 | "Full": "Open Your Brain to Psychic Maelstrom", 20 | "Miss": "You open your brain to static. ***Prepare for the Worst.***", 21 | "Hit": "The MC **gives you an impression** of something new and interesting about the current situation, and might ask you a question or two; answer them. If you know all there is to know, the MC will tell you.", 22 | "Crit": "The MC provides **great detail** about something new and interesting about the current situation, and might ask you a question or two; answer them. If you know all there is to know, the MC will tell you." 23 | }, 24 | { 25 | "Name": "!readperson", 26 | "Full": "Read a Person", 27 | "Miss": "**Ask these questions of the character, but prepare for the worst**:\n* Is your character telling the truth?\n* What’s your character really feeling?\n* What does your character intend to do?\n* What does your character wish I’d do?\n* How could I get your character to __?\n", 28 | "Hit": "Hold 1. **While you are interacting with the character, spend hold 1 for 1 to ask these questions**:\n* Is your character telling the truth?\n* What’s your character really feeling?\n* What does your character intend to do?\n* What does your character wish I’d do?\n* How could I get your character to __?\n", 29 | "Crit": "Hold 3. **While you are interacting with the character, spend hold 1 for 1 to ask these questions**:\n* Is your character telling the truth?\n* What’s your character really feeling?\n* What does your character intend to do?\n* What does your character wish I’d do?\n* How could I get your character to __?\n" 30 | }, 31 | { 32 | "Name": "!help", 33 | "Full": "Help Someone", 34 | "Miss": "Your efforts to help have gone awry. ***Prepare for the Worst.***", 35 | "Hit": "The person you help takes **+1 to their roll.**", 36 | "Crit": "The person you help takes **+2 to their roll.**" 37 | }, 38 | { 39 | "Name": "!interfere", 40 | "Full": "Interfere with Someone", 41 | "Miss": "Your efforts to interfere have gone awry. ***Prepare for the Worst.***", 42 | "Hit": "The person you interfere with takes **-1 to their roll.**", 43 | "Crit": "The person you interfere with takes **-2 to their roll.**" 44 | }, 45 | { 46 | "Name": "!underfire", 47 | "Full": "Do Something Under Fire", 48 | "Miss": "You falter under fire. ***Prepare for the Worst.***", 49 | "Hit": "You flinch, hesitate, or stall: the MC **can offer you a worse outcome or an ugly choice.**", 50 | "Crit": "You do it." 51 | }, 52 | { 53 | "Name": "!seducenpc", 54 | "Full": "Seduce or Manipulate an NPC", 55 | "Miss": "Your trickery fails. ***Prepare for the Worst.***", 56 | "Hit": "They’ll go along with you, but they need some concrete assurance, corroboration, or evidence first.", 57 | "Crit": "They'll go along with you, unless or until some fact or action betrays the reason you gave them." 58 | }, 59 | { 60 | "Name": "!seducepc", 61 | "Full": "Seduce or Manipulate a PC", 62 | "Miss": "Your trickery fails. ***Prepare for the Worst.***", 63 | "Hit": "**Choose one:**\n* If they go along with you, they mark experience.\n* If they refuse, erase one of their stat highlights for the remainder of the session.\n* *What they do is up to them.*", 64 | "Crit": "**Both are true**\n* If they go along with you, they mark experience.\n* If they refuse, erase one of their stat highlights for the remainder of the session.\n* *What they do is up to them.*" 65 | } 66 | ] 67 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/bwmarrin/discordgo" 7 | "github.com/gamefiend/apocalyptica/pkg/moves" 8 | "log" 9 | "net/http" 10 | "os" 11 | "os/signal" 12 | "regexp" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | "syscall" 17 | "time" 18 | ) 19 | 20 | var ( 21 | Intro = flag.Bool("intro", false, "Channel Introductions") 22 | ) 23 | 24 | var Apoc = moves.LoadMoves("basic.json") 25 | var ApocList = MakeList(Apoc) 26 | var Announce = make(map[string]bool) 27 | 28 | func MakeList(mv moves.Move) []string { 29 | l := make([]string, 0) 30 | for _, c := range mv { 31 | l = append(l, fmt.Sprintf("**%s** (%s)", c.Full, c.Name)) 32 | } 33 | return l 34 | } 35 | 36 | func getBonus(s string) int { 37 | reg := regexp.MustCompile(`(?P-?\d+)`) 38 | match := reg.FindStringSubmatch(s) 39 | result := make(map[string]string) 40 | if match == nil { 41 | return 0 42 | } 43 | for i, name := range reg.SubexpNames() { 44 | if i != 0 { 45 | result[name] = match[i] 46 | } 47 | } 48 | fmt.Printf("bonus is %+v\n", result["bonus"]) 49 | bns, e := strconv.Atoi(result["bonus"]) 50 | if e != nil { 51 | return 0 52 | } 53 | return bns 54 | } 55 | 56 | // Addhandler fires this up when Apocalyptica connects to another channel. 57 | func onReady(s *discordgo.Session, m *discordgo.Ready) { 58 | greetings := "**Apocalyptica**. Apocalypse World 2e bot. !!help for instructions, !moves for moves." 59 | log.Printf("Invited to %s servers\n", len(m.Guilds)) 60 | for _, i := range m.Guilds { 61 | log.Printf("CONNECT [%s]\n", i.Name) 62 | // Wait for a few seconds to finish connection, otherwise we miss info. 63 | time.Sleep(2 * time.Second) 64 | for _, c := range i.Channels { 65 | log.Printf("%s.%s [%s]\n", i.Name, c.Name, c.ID) 66 | if c.Type == "text" && *Intro == true { 67 | s.ChannelMessageSend(c.ID, greetings) 68 | fmt.Printf("%s.%s Introduction\n", i.Name, c.Name) 69 | } 70 | } 71 | } 72 | } 73 | 74 | // Addhandler sends a message to this function any time a message on a channel this bot is listening to is created. 75 | func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { 76 | 77 | // Ignore our own messages 78 | if m.Author.ID == s.State.User.ID { 79 | return 80 | } 81 | //here is where look for commands. 82 | // going to move this to another package, but just hardwiring this for an MVP right now. 83 | switch { 84 | case m.Content == "!!help": 85 | help := []string{ 86 | "**Apocalyptica**. Apocalypse World 2e bot. !!help for instructions, !moves for moves.", 87 | "To use, type ! ", 88 | "**examples:**", 89 | "* !goaggro -1", 90 | "* !goaggro 1", 91 | "*!moves* shows which moves I currently support.", 92 | } 93 | s.ChannelMessageSend(m.ChannelID, strings.Join(help, "\n")) 94 | 95 | case m.Content == "!moves": 96 | help := "I currently support:" 97 | s.ChannelMessageSend(m.ChannelID, help) 98 | s.ChannelMessageSend(m.ChannelID, strings.Join(ApocList, "\n")) 99 | default: 100 | moveMsg := moves.FindMove(m.Content, Apoc) 101 | if len(moveMsg) > 0 { 102 | bonus := getBonus(m.Content) 103 | result := moveMsg.Roll(bonus) 104 | d := moveMsg.Display(result, bonus) 105 | s.ChannelMessageSend(m.ChannelID, d) 106 | log.Println(m.ChannelID, m.Author.ID, result, bonus) 107 | } 108 | } 109 | } 110 | 111 | func init() { 112 | flag.Parse() 113 | } 114 | 115 | func main() { 116 | var wg sync.WaitGroup 117 | //Grab Token from the Environment 118 | Token := os.Getenv("DISCORD_TOKEN") 119 | if len(Token) == 0 { 120 | fmt.Println("environment variable DISCORD_TOKEN not set") 121 | os.Exit(1) 122 | } 123 | //launch a small web server 124 | 125 | go func() { 126 | s, e := os.Getwd() 127 | if e != nil { 128 | log.Println("Can't access working directory", e) 129 | } 130 | httpwd := fmt.Sprintf("%s/static", s) 131 | fmt.Println(httpwd) 132 | fs := http.FileServer(http.Dir(httpwd)) 133 | http.Handle("/", fs) 134 | log.Printf("listening on port 8080") 135 | http.ListenAndServe(":8080", nil) 136 | }() 137 | // new discord session with the provided bot token. 138 | dg, err := discordgo.New("Bot " + Token) 139 | if err != nil { 140 | log.Println("error creating Discord session,", err) 141 | } 142 | 143 | // Register the messageCreate func for a callbackto MessageCreate events 144 | dg.AddHandler(messageCreate) 145 | dg.AddHandler(onReady) 146 | // listen on that websocket connection! 147 | err = dg.Open() 148 | if err != nil { 149 | fmt.Println("Error opening connection,", err) 150 | return 151 | } 152 | 153 | //wait until we get Ctl-C/term signal 154 | fmt.Println( 155 | `Loading Moves... 156 | Apocalyptica is Barfing into your channel now... 157 | Press Ctl-C to exit.`) 158 | wg.Add(1) 159 | sc := make(chan os.Signal, 1) 160 | signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) 161 | signals := <-sc 162 | dg.Close() 163 | fmt.Println("recieved ", signals) 164 | fmt.Println("Stopping....") 165 | wg.Done() 166 | } 167 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/importer.go: -------------------------------------------------------------------------------- 1 | package moq 2 | 3 | // taken from https://github.com/ernesto-jimenez/gogen 4 | // Copyright (c) 2015 Ernesto Jiménez 5 | 6 | import ( 7 | "fmt" 8 | "go/ast" 9 | "go/importer" 10 | "go/parser" 11 | "go/token" 12 | "go/types" 13 | "io/ioutil" 14 | "os" 15 | "path" 16 | "path/filepath" 17 | "strings" 18 | ) 19 | 20 | type customImporter struct { 21 | source string 22 | imported map[string]*types.Package 23 | base types.Importer 24 | skipTestFiles bool 25 | } 26 | 27 | func (i *customImporter) Import(path string) (*types.Package, error) { 28 | var err error 29 | if path == "" || path[0] == '.' { 30 | path, err = filepath.Abs(filepath.Clean(path)) 31 | if err != nil { 32 | return nil, err 33 | } 34 | path = stripGopath(path) 35 | } 36 | if pkg, ok := i.imported[path]; ok { 37 | return pkg, nil 38 | } 39 | pkg, err := i.fsPkg(path) 40 | if err != nil { 41 | return nil, err 42 | } 43 | i.imported[path] = pkg 44 | return pkg, nil 45 | } 46 | 47 | func gopathDir(source, pkg string) (string, error) { 48 | // check vendor directory 49 | vendorPath, found := vendorPath(source, pkg) 50 | if found { 51 | return vendorPath, nil 52 | } 53 | for _, gopath := range gopaths() { 54 | absPath, err := filepath.Abs(path.Join(gopath, "src", pkg)) 55 | if err != nil { 56 | return "", err 57 | } 58 | if dir, err := os.Stat(absPath); err == nil && dir.IsDir() { 59 | return absPath, nil 60 | } 61 | } 62 | return "", fmt.Errorf("%s not in $GOPATH or %s", pkg, path.Join(source, "vendor")) 63 | } 64 | 65 | func vendorPath(source, pkg string) (string, bool) { 66 | for { 67 | if isGopath(source) { 68 | return "", false 69 | } 70 | var err error 71 | source, err = filepath.Abs(source) 72 | if err != nil { 73 | return "", false 74 | } 75 | vendorPath, err := filepath.Abs(path.Join(source, "vendor", pkg)) 76 | if err != nil { 77 | return "", false 78 | } 79 | if dir, err := os.Stat(vendorPath); err == nil && dir.IsDir() { 80 | return vendorPath, true 81 | } 82 | source = filepath.Dir(source) 83 | } 84 | } 85 | 86 | func removeGopath(p string) string { 87 | for _, gopath := range gopaths() { 88 | p = strings.Replace(p, path.Join(gopath, "src")+"/", "", 1) 89 | } 90 | return p 91 | } 92 | 93 | func gopaths() []string { 94 | return strings.Split(os.Getenv("GOPATH"), string(filepath.ListSeparator)) 95 | } 96 | 97 | func isGopath(path string) bool { 98 | for _, p := range gopaths() { 99 | if p == path { 100 | return true 101 | } 102 | } 103 | return false 104 | } 105 | 106 | func (i *customImporter) fsPkg(pkg string) (*types.Package, error) { 107 | dir, err := gopathDir(i.source, pkg) 108 | if err != nil { 109 | return importOrErr(i.base, pkg, err) 110 | } 111 | 112 | dirFiles, err := ioutil.ReadDir(dir) 113 | if err != nil { 114 | return importOrErr(i.base, pkg, err) 115 | } 116 | 117 | fset := token.NewFileSet() 118 | var files []*ast.File 119 | for _, fileInfo := range dirFiles { 120 | if fileInfo.IsDir() { 121 | continue 122 | } 123 | n := fileInfo.Name() 124 | if path.Ext(fileInfo.Name()) != ".go" { 125 | continue 126 | } 127 | if i.skipTestFiles && strings.Contains(fileInfo.Name(), "_test.go") { 128 | continue 129 | } 130 | file := path.Join(dir, n) 131 | src, err := ioutil.ReadFile(file) 132 | if err != nil { 133 | return nil, err 134 | } 135 | f, err := parser.ParseFile(fset, file, src, 0) 136 | if err != nil { 137 | return nil, err 138 | } 139 | files = append(files, f) 140 | } 141 | conf := types.Config{ 142 | Importer: i, 143 | } 144 | p, err := conf.Check(pkg, fset, files, nil) 145 | 146 | if err != nil { 147 | return importOrErr(i.base, pkg, err) 148 | } 149 | return p, nil 150 | } 151 | 152 | func importOrErr(base types.Importer, pkg string, err error) (*types.Package, error) { 153 | p, impErr := base.Import(pkg) 154 | if impErr != nil { 155 | return nil, err 156 | } 157 | return p, nil 158 | } 159 | 160 | // newImporter returns an importer that will try to import code from gopath before using go/importer.Default and skipping test files 161 | func newImporter(source string) types.Importer { 162 | return &customImporter{ 163 | source: source, 164 | imported: make(map[string]*types.Package), 165 | base: importer.Default(), 166 | skipTestFiles: true, 167 | } 168 | } 169 | 170 | // // DefaultWithTestFiles same as Default but it parses test files too 171 | // func DefaultWithTestFiles() types.Importer { 172 | // return &customImporter{ 173 | // imported: make(map[string]*types.Package), 174 | // base: importer.Default(), 175 | // skipTestFiles: false, 176 | // } 177 | // } 178 | 179 | // stripGopath teks the directory to a package and remove the gopath to get the 180 | // canonical package name. 181 | func stripGopath(p string) string { 182 | for _, gopath := range gopaths() { 183 | p = strings.TrimPrefix(p, path.Join(gopath, "src")+"/") 184 | } 185 | return p 186 | } 187 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/discord.go: -------------------------------------------------------------------------------- 1 | // Discordgo - Discord bindings for Go 2 | // Available at https://github.com/bwmarrin/discordgo 3 | 4 | // Copyright 2015-2016 Bruce Marriner . All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | // This file contains high level helper functions and easy entry points for the 9 | // entire discordgo package. These functions are beling developed and are very 10 | // experimental at this point. They will most likley change so please use the 11 | // low level functions if that's a problem. 12 | 13 | // Package discordgo provides Discord binding for Go 14 | package discordgo 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | "net/http" 20 | "time" 21 | ) 22 | 23 | // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) 24 | const VERSION = "0.17.0" 25 | 26 | // ErrMFA will be risen by New when the user has 2FA. 27 | var ErrMFA = errors.New("account has 2FA enabled") 28 | 29 | // New creates a new Discord session and will automate some startup 30 | // tasks if given enough information to do so. Currently you can pass zero 31 | // arguments and it will return an empty Discord session. 32 | // There are 3 ways to call New: 33 | // With a single auth token - All requests will use the token blindly, 34 | // no verification of the token will be done and requests may fail. 35 | // IF THE TOKEN IS FOR A BOT, IT MUST BE PREFIXED WITH `BOT ` 36 | // eg: `"Bot "` 37 | // With an email and password - Discord will sign in with the provided 38 | // credentials. 39 | // With an email, password and auth token - Discord will verify the auth 40 | // token, if it is invalid it will sign in with the provided 41 | // credentials. This is the Discord recommended way to sign in. 42 | // 43 | // NOTE: While email/pass authentication is supported by DiscordGo it is 44 | // HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token 45 | // and then use that authentication token for all future connections. 46 | // Also, doing any form of automation with a user (non Bot) account may result 47 | // in that account being permanently banned from Discord. 48 | func New(args ...interface{}) (s *Session, err error) { 49 | 50 | // Create an empty Session interface. 51 | s = &Session{ 52 | State: NewState(), 53 | ratelimiter: NewRatelimiter(), 54 | StateEnabled: true, 55 | Compress: true, 56 | ShouldReconnectOnError: true, 57 | ShardID: 0, 58 | ShardCount: 1, 59 | MaxRestRetries: 3, 60 | Client: &http.Client{Timeout: (20 * time.Second)}, 61 | sequence: new(int64), 62 | LastHeartbeatAck: time.Now().UTC(), 63 | } 64 | 65 | // If no arguments are passed return the empty Session interface. 66 | if args == nil { 67 | return 68 | } 69 | 70 | // Variables used below when parsing func arguments 71 | var auth, pass string 72 | 73 | // Parse passed arguments 74 | for _, arg := range args { 75 | 76 | switch v := arg.(type) { 77 | 78 | case []string: 79 | if len(v) > 3 { 80 | err = fmt.Errorf("too many string parameters provided") 81 | return 82 | } 83 | 84 | // First string is either token or username 85 | if len(v) > 0 { 86 | auth = v[0] 87 | } 88 | 89 | // If second string exists, it must be a password. 90 | if len(v) > 1 { 91 | pass = v[1] 92 | } 93 | 94 | // If third string exists, it must be an auth token. 95 | if len(v) > 2 { 96 | s.Token = v[2] 97 | } 98 | 99 | case string: 100 | // First string must be either auth token or username. 101 | // Second string must be a password. 102 | // Only 2 input strings are supported. 103 | 104 | if auth == "" { 105 | auth = v 106 | } else if pass == "" { 107 | pass = v 108 | } else if s.Token == "" { 109 | s.Token = v 110 | } else { 111 | err = fmt.Errorf("too many string parameters provided") 112 | return 113 | } 114 | 115 | // case Config: 116 | // TODO: Parse configuration struct 117 | 118 | default: 119 | err = fmt.Errorf("unsupported parameter type provided") 120 | return 121 | } 122 | } 123 | 124 | // If only one string was provided, assume it is an auth token. 125 | // Otherwise get auth token from Discord, if a token was specified 126 | // Discord will verify it for free, or log the user in if it is 127 | // invalid. 128 | if pass == "" { 129 | s.Token = auth 130 | } else { 131 | err = s.Login(auth, pass) 132 | if err != nil || s.Token == "" { 133 | if s.MFA { 134 | err = ErrMFA 135 | } else { 136 | err = fmt.Errorf("Unable to fetch discord authentication token. %v", err) 137 | } 138 | return 139 | } 140 | } 141 | 142 | // The Session is now able to have RestAPI methods called on it. 143 | // It is recommended that you now call Open() so that events will trigger. 144 | 145 | return 146 | } 147 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/ratelimit.go: -------------------------------------------------------------------------------- 1 | package discordgo 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "strings" 7 | "sync" 8 | "sync/atomic" 9 | "time" 10 | ) 11 | 12 | // customRateLimit holds information for defining a custom rate limit 13 | type customRateLimit struct { 14 | suffix string 15 | requests int 16 | reset time.Duration 17 | } 18 | 19 | // RateLimiter holds all ratelimit buckets 20 | type RateLimiter struct { 21 | sync.Mutex 22 | global *int64 23 | buckets map[string]*Bucket 24 | globalRateLimit time.Duration 25 | customRateLimits []*customRateLimit 26 | } 27 | 28 | // NewRatelimiter returns a new RateLimiter 29 | func NewRatelimiter() *RateLimiter { 30 | 31 | return &RateLimiter{ 32 | buckets: make(map[string]*Bucket), 33 | global: new(int64), 34 | customRateLimits: []*customRateLimit{ 35 | &customRateLimit{ 36 | suffix: "//reactions//", 37 | requests: 1, 38 | reset: 200 * time.Millisecond, 39 | }, 40 | }, 41 | } 42 | } 43 | 44 | // getBucket retrieves or creates a bucket 45 | func (r *RateLimiter) getBucket(key string) *Bucket { 46 | r.Lock() 47 | defer r.Unlock() 48 | 49 | if bucket, ok := r.buckets[key]; ok { 50 | return bucket 51 | } 52 | 53 | b := &Bucket{ 54 | remaining: 1, 55 | Key: key, 56 | global: r.global, 57 | } 58 | 59 | // Check if there is a custom ratelimit set for this bucket ID. 60 | for _, rl := range r.customRateLimits { 61 | if strings.HasSuffix(b.Key, rl.suffix) { 62 | b.customRateLimit = rl 63 | break 64 | } 65 | } 66 | 67 | r.buckets[key] = b 68 | return b 69 | } 70 | 71 | // LockBucket Locks until a request can be made 72 | func (r *RateLimiter) LockBucket(bucketID string) *Bucket { 73 | 74 | b := r.getBucket(bucketID) 75 | 76 | b.Lock() 77 | 78 | // If we ran out of calls and the reset time is still ahead of us 79 | // then we need to take it easy and relax a little 80 | if b.remaining < 1 && b.reset.After(time.Now()) { 81 | time.Sleep(b.reset.Sub(time.Now())) 82 | 83 | } 84 | 85 | // Check for global ratelimits 86 | sleepTo := time.Unix(0, atomic.LoadInt64(r.global)) 87 | if now := time.Now(); now.Before(sleepTo) { 88 | time.Sleep(sleepTo.Sub(now)) 89 | } 90 | 91 | b.remaining-- 92 | return b 93 | } 94 | 95 | // Bucket represents a ratelimit bucket, each bucket gets ratelimited individually (-global ratelimits) 96 | type Bucket struct { 97 | sync.Mutex 98 | Key string 99 | remaining int 100 | limit int 101 | reset time.Time 102 | global *int64 103 | 104 | lastReset time.Time 105 | customRateLimit *customRateLimit 106 | } 107 | 108 | // Release unlocks the bucket and reads the headers to update the buckets ratelimit info 109 | // and locks up the whole thing in case if there's a global ratelimit. 110 | func (b *Bucket) Release(headers http.Header) error { 111 | defer b.Unlock() 112 | 113 | // Check if the bucket uses a custom ratelimiter 114 | if rl := b.customRateLimit; rl != nil { 115 | if time.Now().Sub(b.lastReset) >= rl.reset { 116 | b.remaining = rl.requests - 1 117 | b.lastReset = time.Now() 118 | } 119 | if b.remaining < 1 { 120 | b.reset = time.Now().Add(rl.reset) 121 | } 122 | return nil 123 | } 124 | 125 | if headers == nil { 126 | return nil 127 | } 128 | 129 | remaining := headers.Get("X-RateLimit-Remaining") 130 | reset := headers.Get("X-RateLimit-Reset") 131 | global := headers.Get("X-RateLimit-Global") 132 | retryAfter := headers.Get("Retry-After") 133 | 134 | // Update global and per bucket reset time if the proper headers are available 135 | // If global is set, then it will block all buckets until after Retry-After 136 | // If Retry-After without global is provided it will use that for the new reset 137 | // time since it's more accurate than X-RateLimit-Reset. 138 | // If Retry-After after is not proided, it will update the reset time from X-RateLimit-Reset 139 | if retryAfter != "" { 140 | parsedAfter, err := strconv.ParseInt(retryAfter, 10, 64) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | resetAt := time.Now().Add(time.Duration(parsedAfter) * time.Millisecond) 146 | 147 | // Lock either this single bucket or all buckets 148 | if global != "" { 149 | atomic.StoreInt64(b.global, resetAt.UnixNano()) 150 | } else { 151 | b.reset = resetAt 152 | } 153 | } else if reset != "" { 154 | // Calculate the reset time by using the date header returned from discord 155 | discordTime, err := http.ParseTime(headers.Get("Date")) 156 | if err != nil { 157 | return err 158 | } 159 | 160 | unix, err := strconv.ParseInt(reset, 10, 64) 161 | if err != nil { 162 | return err 163 | } 164 | 165 | // Calculate the time until reset and add it to the current local time 166 | // some extra time is added because without it i still encountered 429's. 167 | // The added amount is the lowest amount that gave no 429's 168 | // in 1k requests 169 | delta := time.Unix(unix, 0).Sub(discordTime) + time.Millisecond*250 170 | b.reset = time.Now().Add(delta) 171 | } 172 | 173 | // Udpate remaining if header is present 174 | if remaining != "" { 175 | parsedRemaining, err := strconv.ParseInt(remaining, 10, 32) 176 | if err != nil { 177 | return err 178 | } 179 | b.remaining = int(parsedRemaining) 180 | } 181 | 182 | return nil 183 | } 184 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "os" 9 | "os/signal" 10 | "strings" 11 | "syscall" 12 | "time" 13 | 14 | "github.com/bwmarrin/discordgo" 15 | ) 16 | 17 | func init() { 18 | flag.StringVar(&token, "t", "", "Bot Token") 19 | flag.Parse() 20 | } 21 | 22 | var token string 23 | var buffer = make([][]byte, 0) 24 | 25 | func main() { 26 | 27 | if token == "" { 28 | fmt.Println("No token provided. Please run: airhorn -t ") 29 | return 30 | } 31 | 32 | // Load the sound file. 33 | err := loadSound() 34 | if err != nil { 35 | fmt.Println("Error loading sound: ", err) 36 | fmt.Println("Please copy $GOPATH/src/github.com/bwmarrin/examples/airhorn/airhorn.dca to this directory.") 37 | return 38 | } 39 | 40 | // Create a new Discord session using the provided bot token. 41 | dg, err := discordgo.New("Bot " + token) 42 | if err != nil { 43 | fmt.Println("Error creating Discord session: ", err) 44 | return 45 | } 46 | 47 | // Register ready as a callback for the ready events. 48 | dg.AddHandler(ready) 49 | 50 | // Register messageCreate as a callback for the messageCreate events. 51 | dg.AddHandler(messageCreate) 52 | 53 | // Register guildCreate as a callback for the guildCreate events. 54 | dg.AddHandler(guildCreate) 55 | 56 | // Open the websocket and begin listening. 57 | err = dg.Open() 58 | if err != nil { 59 | fmt.Println("Error opening Discord session: ", err) 60 | } 61 | 62 | // Wait here until CTRL-C or other term signal is received. 63 | fmt.Println("Airhorn is now running. Press CTRL-C to exit.") 64 | sc := make(chan os.Signal, 1) 65 | signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) 66 | <-sc 67 | 68 | // Cleanly close down the Discord session. 69 | dg.Close() 70 | } 71 | 72 | // This function will be called (due to AddHandler above) when the bot receives 73 | // the "ready" event from Discord. 74 | func ready(s *discordgo.Session, event *discordgo.Ready) { 75 | 76 | // Set the playing status. 77 | s.UpdateStatus(0, "!airhorn") 78 | } 79 | 80 | // This function will be called (due to AddHandler above) every time a new 81 | // message is created on any channel that the autenticated bot has access to. 82 | func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { 83 | 84 | // Ignore all messages created by the bot itself 85 | // This isn't required in this specific example but it's a good practice. 86 | if m.Author.ID == s.State.User.ID { 87 | return 88 | } 89 | 90 | // check if the message is "!airhorn" 91 | if strings.HasPrefix(m.Content, "!airhorn") { 92 | 93 | // Find the channel that the message came from. 94 | c, err := s.State.Channel(m.ChannelID) 95 | if err != nil { 96 | // Could not find channel. 97 | return 98 | } 99 | 100 | // Find the guild for that channel. 101 | g, err := s.State.Guild(c.GuildID) 102 | if err != nil { 103 | // Could not find guild. 104 | return 105 | } 106 | 107 | // Look for the message sender in that guild's current voice states. 108 | for _, vs := range g.VoiceStates { 109 | if vs.UserID == m.Author.ID { 110 | err = playSound(s, g.ID, vs.ChannelID) 111 | if err != nil { 112 | fmt.Println("Error playing sound:", err) 113 | } 114 | 115 | return 116 | } 117 | } 118 | } 119 | } 120 | 121 | // This function will be called (due to AddHandler above) every time a new 122 | // guild is joined. 123 | func guildCreate(s *discordgo.Session, event *discordgo.GuildCreate) { 124 | 125 | if event.Guild.Unavailable { 126 | return 127 | } 128 | 129 | for _, channel := range event.Guild.Channels { 130 | if channel.ID == event.Guild.ID { 131 | _, _ = s.ChannelMessageSend(channel.ID, "Airhorn is ready! Type !airhorn while in a voice channel to play a sound.") 132 | return 133 | } 134 | } 135 | } 136 | 137 | // loadSound attempts to load an encoded sound file from disk. 138 | func loadSound() error { 139 | 140 | file, err := os.Open("airhorn.dca") 141 | if err != nil { 142 | fmt.Println("Error opening dca file :", err) 143 | return err 144 | } 145 | 146 | var opuslen int16 147 | 148 | for { 149 | // Read opus frame length from dca file. 150 | err = binary.Read(file, binary.LittleEndian, &opuslen) 151 | 152 | // If this is the end of the file, just return. 153 | if err == io.EOF || err == io.ErrUnexpectedEOF { 154 | err := file.Close() 155 | if err != nil { 156 | return err 157 | } 158 | return nil 159 | } 160 | 161 | if err != nil { 162 | fmt.Println("Error reading from dca file :", err) 163 | return err 164 | } 165 | 166 | // Read encoded pcm from dca file. 167 | InBuf := make([]byte, opuslen) 168 | err = binary.Read(file, binary.LittleEndian, &InBuf) 169 | 170 | // Should not be any end of file errors 171 | if err != nil { 172 | fmt.Println("Error reading from dca file :", err) 173 | return err 174 | } 175 | 176 | // Append encoded pcm data to the buffer. 177 | buffer = append(buffer, InBuf) 178 | } 179 | } 180 | 181 | // playSound plays the current buffer to the provided channel. 182 | func playSound(s *discordgo.Session, guildID, channelID string) (err error) { 183 | 184 | // Join the provided voice channel. 185 | vc, err := s.ChannelVoiceJoin(guildID, channelID, false, true) 186 | if err != nil { 187 | return err 188 | } 189 | 190 | // Sleep for a specified amount of time before playing the sound 191 | time.Sleep(250 * time.Millisecond) 192 | 193 | // Start speaking. 194 | vc.Speaking(true) 195 | 196 | // Send the buffer data. 197 | for _, buff := range buffer { 198 | vc.OpusSend <- buff 199 | } 200 | 201 | // Stop speaking 202 | vc.Speaking(false) 203 | 204 | // Sleep for a specificed amount of time before ending. 205 | time.Sleep(250 * time.Millisecond) 206 | 207 | // Disconnect from the provided voice channel. 208 | vc.Disconnect() 209 | 210 | return nil 211 | } 212 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/README.md: -------------------------------------------------------------------------------- 1 | # DiscordGo 2 | 3 | [![GoDoc](https://godoc.org/github.com/bwmarrin/discordgo?status.svg)](https://godoc.org/github.com/bwmarrin/discordgo) [![Go report](http://goreportcard.com/badge/bwmarrin/discordgo)](http://goreportcard.com/report/bwmarrin/discordgo) [![Build Status](https://travis-ci.org/bwmarrin/discordgo.svg?branch=master)](https://travis-ci.org/bwmarrin/discordgo) [![Discord Gophers](https://img.shields.io/badge/Discord%20Gophers-%23discordgo-blue.svg)](https://discord.gg/0f1SbxBZjYoCtNPP) [![Discord API](https://img.shields.io/badge/Discord%20API-%23go_discordgo-blue.svg)](https://discord.gg/0SBTUU1wZTWT6sqd) 4 | 5 | 6 | 7 | DiscordGo is a [Go](https://golang.org/) package that provides low level 8 | bindings to the [Discord](https://discordapp.com/) chat client API. DiscordGo 9 | has nearly complete support for all of the Discord API endpoints, websocket 10 | interface, and voice interface. 11 | 12 | If you would like to help the DiscordGo package please use 13 | [this link](https://discordapp.com/oauth2/authorize?client_id=173113690092994561&scope=bot) 14 | to add the official DiscordGo test bot **dgo** to your server. This provides 15 | indispensable help to this project. 16 | 17 | * See [dgVoice](https://github.com/bwmarrin/dgvoice) package for an example of 18 | additional voice helper functions and features for DiscordGo 19 | 20 | * See [dca](https://github.com/bwmarrin/dca) for an **experimental** stand alone 21 | tool that wraps `ffmpeg` to create opus encoded audio appropriate for use with 22 | Discord (and DiscordGo) 23 | 24 | **For help with this package or general Go discussion, please join the [Discord 25 | Gophers](https://discord.gg/0f1SbxBZjYq9jLBk) chat server.** 26 | 27 | ## Getting Started 28 | 29 | ### master vs develop Branch 30 | * The master branch represents the latest released version of DiscordGo. This 31 | branch will always have a stable and tested version of the library. Each release 32 | is tagged and you can easily download a specific release and view release notes 33 | on the github [releases](https://github.com/bwmarrin/discordgo/releases) page. 34 | 35 | * The develop branch is where all development happens and almost always has 36 | new features over the master branch. However breaking changes are frequently 37 | added to develop and even sometimes bugs are introduced. Bugs get fixed and 38 | the breaking changes get documented before pushing to master. 39 | 40 | *So, what should you use?* 41 | 42 | If you can accept the constant changing nature of *develop* then it is the 43 | recommended branch to use. Otherwise, if you want to tail behind development 44 | slightly and have a more stable package with documented releases then use *master* 45 | 46 | ### Installing 47 | 48 | This assumes you already have a working Go environment, if not please see 49 | [this page](https://golang.org/doc/install) first. 50 | 51 | `go get` *will always pull the latest released version from the master branch.* 52 | 53 | ```sh 54 | go get github.com/bwmarrin/discordgo 55 | ``` 56 | 57 | If you want to use the develop branch, follow these steps next. 58 | 59 | ```sh 60 | cd $GOPATH/src/github.com/bwmarrin/discordgo 61 | git checkout develop 62 | ``` 63 | 64 | ### Usage 65 | 66 | Import the package into your project. 67 | 68 | ```go 69 | import "github.com/bwmarrin/discordgo" 70 | ``` 71 | 72 | Construct a new Discord client which can be used to access the variety of 73 | Discord API functions and to set callback functions for Discord events. 74 | 75 | ```go 76 | discord, err := discordgo.New("authentication token") 77 | ``` 78 | 79 | See Documentation and Examples below for more detailed information. 80 | 81 | 82 | ## Documentation 83 | 84 | **NOTICE** : This library and the Discord API are unfinished. 85 | Because of that there may be major changes to library in the future. 86 | 87 | The DiscordGo code is fairly well documented at this point and is currently 88 | the only documentation available. Both GoDoc and GoWalker (below) present 89 | that information in a nice format. 90 | 91 | - [![GoDoc](https://godoc.org/github.com/bwmarrin/discordgo?status.svg)](https://godoc.org/github.com/bwmarrin/discordgo) 92 | - [![Go Walker](http://gowalker.org/api/v1/badge)](https://gowalker.org/github.com/bwmarrin/discordgo) 93 | - Hand crafted documentation coming eventually. 94 | 95 | 96 | ## Examples 97 | 98 | Below is a list of examples and other projects using DiscordGo. Please submit 99 | an issue if you would like your project added or removed from this list 100 | 101 | - [DiscordGo Examples](https://github.com/bwmarrin/discordgo/tree/master/examples) A collection of example programs written with DiscordGo 102 | - [Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo) A curated list of high quality projects using DiscordGo 103 | 104 | ## Troubleshooting 105 | For help with common problems please reference the 106 | [Troubleshooting](https://github.com/bwmarrin/discordgo/wiki/Troubleshooting) 107 | section of the project wiki. 108 | 109 | 110 | ## Contributing 111 | Contributions are very welcomed, however please follow the below guidelines. 112 | 113 | - First open an issue describing the bug or enhancement so it can be 114 | discussed. 115 | - Fork the develop branch and make your changes. 116 | - Try to match current naming conventions as closely as possible. 117 | - This package is intended to be a low level direct mapping of the Discord API 118 | so please avoid adding enhancements outside of that scope without first 119 | discussing it. 120 | - Create a Pull Request with your changes against the develop branch. 121 | 122 | 123 | ## List of Discord APIs 124 | 125 | See [this chart](https://abal.moe/Discord/Libraries.html) for a feature 126 | comparison and list of other Discord API libraries. 127 | 128 | ## Special Thanks 129 | 130 | [Chris Rhodes](https://github.com/iopred) - For the DiscordGo logo and tons of PRs 131 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/moq.go: -------------------------------------------------------------------------------- 1 | package moq 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "go/ast" 8 | "go/format" 9 | "go/parser" 10 | "go/token" 11 | "go/types" 12 | "io" 13 | "os" 14 | "strings" 15 | "text/template" 16 | ) 17 | 18 | // Mocker can generate mock structs. 19 | type Mocker struct { 20 | src string 21 | tmpl *template.Template 22 | fset *token.FileSet 23 | pkgs map[string]*ast.Package 24 | pkgName string 25 | 26 | imports map[string]bool 27 | } 28 | 29 | // New makes a new Mocker for the specified package directory. 30 | func New(src, packageName string) (*Mocker, error) { 31 | fset := token.NewFileSet() 32 | noTestFiles := func(i os.FileInfo) bool { 33 | return !strings.HasSuffix(i.Name(), "_test.go") 34 | } 35 | pkgs, err := parser.ParseDir(fset, src, noTestFiles, parser.SpuriousErrors) 36 | if err != nil { 37 | return nil, err 38 | } 39 | if len(packageName) == 0 { 40 | for pkgName := range pkgs { 41 | if strings.Contains(pkgName, "_test") { 42 | continue 43 | } 44 | packageName = pkgName 45 | break 46 | } 47 | } 48 | if len(packageName) == 0 { 49 | return nil, errors.New("failed to determine package name") 50 | } 51 | tmpl, err := template.New("moq").Funcs(templateFuncs).Parse(moqTemplate) 52 | if err != nil { 53 | return nil, err 54 | } 55 | return &Mocker{ 56 | src: src, 57 | tmpl: tmpl, 58 | fset: fset, 59 | pkgs: pkgs, 60 | pkgName: packageName, 61 | imports: make(map[string]bool), 62 | }, nil 63 | } 64 | 65 | // Mock generates a mock for the specified interface name. 66 | func (m *Mocker) Mock(w io.Writer, name ...string) error { 67 | if len(name) == 0 { 68 | return errors.New("must specify one interface") 69 | } 70 | doc := doc{ 71 | PackageName: m.pkgName, 72 | Imports: moqImports, 73 | } 74 | mocksMethods := false 75 | for _, pkg := range m.pkgs { 76 | i := 0 77 | files := make([]*ast.File, len(pkg.Files)) 78 | for _, file := range pkg.Files { 79 | files[i] = file 80 | i++ 81 | } 82 | conf := types.Config{Importer: newImporter(m.src)} 83 | tpkg, err := conf.Check(m.src, m.fset, files, nil) 84 | if err != nil { 85 | return err 86 | } 87 | for _, n := range name { 88 | iface := tpkg.Scope().Lookup(n) 89 | if iface == nil { 90 | return fmt.Errorf("cannot find interface %s", n) 91 | } 92 | if !types.IsInterface(iface.Type()) { 93 | return fmt.Errorf("%s (%s) not an interface", n, iface.Type().String()) 94 | } 95 | iiface := iface.Type().Underlying().(*types.Interface).Complete() 96 | obj := obj{ 97 | InterfaceName: n, 98 | } 99 | for i := 0; i < iiface.NumMethods(); i++ { 100 | mocksMethods = true 101 | meth := iiface.Method(i) 102 | sig := meth.Type().(*types.Signature) 103 | method := &method{ 104 | Name: meth.Name(), 105 | } 106 | obj.Methods = append(obj.Methods, method) 107 | method.Params = m.extractArgs(sig, sig.Params(), "in%d") 108 | method.Returns = m.extractArgs(sig, sig.Results(), "out%d") 109 | } 110 | doc.Objects = append(doc.Objects, obj) 111 | } 112 | } 113 | if mocksMethods { 114 | doc.Imports = append(doc.Imports, "sync") 115 | } 116 | for pkgToImport := range m.imports { 117 | doc.Imports = append(doc.Imports, pkgToImport) 118 | } 119 | var buf bytes.Buffer 120 | err := m.tmpl.Execute(&buf, doc) 121 | if err != nil { 122 | return err 123 | } 124 | formatted, err := format.Source(buf.Bytes()) 125 | if err != nil { 126 | return fmt.Errorf("go/format: %s", err) 127 | } 128 | if _, err := w.Write(formatted); err != nil { 129 | return err 130 | } 131 | return nil 132 | } 133 | 134 | func (m *Mocker) packageQualifier(pkg *types.Package) string { 135 | if m.pkgName == pkg.Name() { 136 | return "" 137 | } 138 | path := pkg.Path() 139 | if pkg.Path() == "." { 140 | wd, err := os.Getwd() 141 | if err == nil { 142 | path = stripGopath(wd) 143 | } 144 | } 145 | m.imports[path] = true 146 | return pkg.Name() 147 | } 148 | 149 | func (m *Mocker) extractArgs(sig *types.Signature, list *types.Tuple, nameFormat string) []*param { 150 | var params []*param 151 | listLen := list.Len() 152 | for ii := 0; ii < listLen; ii++ { 153 | p := list.At(ii) 154 | name := p.Name() 155 | if name == "" { 156 | name = fmt.Sprintf(nameFormat, ii+1) 157 | } 158 | typename := types.TypeString(p.Type(), m.packageQualifier) 159 | // check for final variadic argument 160 | variadic := sig.Variadic() && ii == listLen-1 && typename[0:2] == "[]" 161 | param := ¶m{ 162 | Name: name, 163 | Type: typename, 164 | Variadic: variadic, 165 | } 166 | params = append(params, param) 167 | } 168 | return params 169 | } 170 | 171 | type doc struct { 172 | PackageName string 173 | Objects []obj 174 | Imports []string 175 | } 176 | 177 | type obj struct { 178 | InterfaceName string 179 | Methods []*method 180 | } 181 | type method struct { 182 | Name string 183 | Params []*param 184 | Returns []*param 185 | } 186 | 187 | func (m *method) Arglist() string { 188 | params := make([]string, len(m.Params)) 189 | for i, p := range m.Params { 190 | params[i] = p.String() 191 | } 192 | return strings.Join(params, ", ") 193 | } 194 | 195 | func (m *method) ArgCallList() string { 196 | params := make([]string, len(m.Params)) 197 | for i, p := range m.Params { 198 | params[i] = p.CallName() 199 | } 200 | return strings.Join(params, ", ") 201 | } 202 | 203 | func (m *method) ReturnArglist() string { 204 | params := make([]string, len(m.Returns)) 205 | for i, p := range m.Returns { 206 | params[i] = p.TypeString() 207 | } 208 | if len(m.Returns) > 1 { 209 | return fmt.Sprintf("(%s)", strings.Join(params, ", ")) 210 | } 211 | return strings.Join(params, ", ") 212 | } 213 | 214 | type param struct { 215 | Name string 216 | Type string 217 | Variadic bool 218 | } 219 | 220 | func (p param) String() string { 221 | return fmt.Sprintf("%s %s", p.Name, p.TypeString()) 222 | } 223 | 224 | func (p param) CallName() string { 225 | if p.Variadic { 226 | return p.Name + "..." 227 | } 228 | return p.Name 229 | } 230 | 231 | func (p param) TypeString() string { 232 | if p.Variadic { 233 | return "..." + p.Type[2:] 234 | } 235 | return p.Type 236 | } 237 | 238 | var templateFuncs = template.FuncMap{ 239 | "Exported": func(s string) string { 240 | if s == "" { 241 | return "" 242 | } 243 | return strings.ToUpper(s[0:1]) + s[1:] 244 | }, 245 | } 246 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/docs/GettingStarted.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | This page is dedicated to helping you get started on your way to making the 4 | next great Discord bot or client with DiscordGo. Once you've done that please 5 | don't forget to submit it to the 6 | [Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo) list :). 7 | 8 | 9 | **First, lets cover a few topics so you can make the best choices on how to 10 | move forward from here.** 11 | 12 | 13 | ### Master vs Develop 14 | **When installing DiscordGo you will need to decide if you want to use the current 15 | master branch or the bleeding edge development branch.** 16 | 17 | * The **master** branch represents the latest released version of DiscordGo. This 18 | branch will always have a stable and tested version of the library. Each 19 | release is tagged and you can easily download a specific release and view the 20 | release notes on the github [releases](https://github.com/bwmarrin/discordgo/releases) 21 | page. 22 | 23 | * The **develop** branch is where all development happens and almost always has 24 | new features over the master branch. However breaking changes are frequently 25 | added the develop branch and sometimes bugs are introduced. Bugs get fixed 26 | and the breaking changes get documented before pushing to master. 27 | 28 | *So, what should you use?* 29 | 30 | Due to the how frequently the Discord API is changing there is a high chance 31 | that the *master* branch may be lacking important features. Because of that, if 32 | you can accept the constant changing nature of the *develop* branch and the 33 | chance that it may occasionally contain bugs then it is the recommended branch 34 | to use. Otherwise, if you want to tail behind development slightly and have a 35 | more stable package with documented releases then please use the *master* 36 | branch instead. 37 | 38 | 39 | ### Client vs Bot 40 | 41 | You probably already know the answer to this but now is a good time to decide 42 | if your goal is to write a client application or a bot. DiscordGo aims to fully 43 | support both client applications and bots but there are some differences 44 | between the two that you should understand. 45 | 46 | #### Client Application 47 | A client application is a program that is intended to be used by a normal user 48 | as a replacement for the official clients that Discord provides. An example of 49 | this would be a terminal client used to read and send messages with your normal 50 | user account or possibly a new desktop client that provides a different set of 51 | features than the official desktop client that Discord already provides. 52 | 53 | Client applications work with normal user accounts and you can login with an 54 | email address and password or a special authentication token. However, normal 55 | user accounts are not allowed to perform any type of automation and doing so can 56 | cause the account to be banned from Discord. Also normal user accounts do not 57 | support multi-server voice connections and some other features that are 58 | exclusive to Bot accounts only. 59 | 60 | To create a new user account (if you have not done so already) visit the 61 | [Discord](https://discordapp.com/) website and click on the 62 | **Try Discord Now, It's Free** button then follow the steps to setup your 63 | new account. 64 | 65 | 66 | #### Bot Application 67 | A bot application is a special program that interacts with the Discord servers 68 | to perform some form of automation or provide some type of service. Examples 69 | are things like number trivia games, music streaming, channel moderation, 70 | sending reminders, playing loud airhorn sounds, comic generators, YouTube 71 | integration, Twitch integration.. You're *almost* only limited by your imagination. 72 | 73 | Bot applications require the use of a special Bot account. These accounts are 74 | tied to your personal user account. Bot accounts cannot login with the normal 75 | user clients and they cannot join servers the same way a user does. They do not 76 | have access to some user client specific features however they gain access to 77 | many Bot specific features. 78 | 79 | To create a new bot account first create yourself a normal user account on 80 | Discord then visit the [My Applications](https://discordapp.com/developers/applications/me) 81 | page and click on the **New Application** box. Follow the prompts from there 82 | to finish creating your account. 83 | 84 | 85 | **More information about Bots vs Client accounts can be found [here](https://discordapp.com/developers/docs/topics/oauth2#bot-vs-user-accounts)** 86 | 87 | # Requirements 88 | 89 | DiscordGo requires Go version 1.4 or higher. It has been tested to compile and 90 | run successfully on Debian Linux 8, FreeBSD 10, and Windows 7. It is expected 91 | that it should work anywhere Go 1.4 or higher works. If you run into problems 92 | please let us know :) 93 | 94 | You must already have a working Go environment setup to use DiscordGo. If you 95 | are new to Go and have not yet installed and tested it on your computer then 96 | please visit [this page](https://golang.org/doc/install) first then I highly 97 | recommend you walk though [A Tour of Go](https://tour.golang.org/welcome/1) to 98 | help get your familiar with the Go language. Also checkout the relevent Go plugin 99 | for your editor - they are hugely helpful when developing Go code. 100 | 101 | * Vim - [vim-go](https://github.com/fatih/vim-go) 102 | * Sublime - [GoSublime](https://github.com/DisposaBoy/GoSublime) 103 | * Atom - [go-plus](https://atom.io/packages/go-plus) 104 | * Visual Studio - [vscode-go](https://github.com/Microsoft/vscode-go) 105 | 106 | 107 | # Install DiscordGo 108 | 109 | Like any other Go package the fist step is to `go get` the package. This will 110 | always pull the latest released version from the master branch. Then run 111 | `go install` to compile and install the libraries on your system. 112 | 113 | #### Linux/BSD 114 | 115 | Run go get to download the package to your GOPATH/src folder. 116 | 117 | ```sh 118 | go get github.com/bwmarrin/discordgo 119 | ``` 120 | 121 | If you want to use the develop branch, follow these steps next. 122 | 123 | ```sh 124 | cd $GOPATH/src/github.com/bwmarrin/discordgo 125 | git checkout develop 126 | ``` 127 | 128 | Finally, compile and install the package into the GOPATH/pkg folder. This isn't 129 | absolutely required but doing this will allow the Go plugin for your editor to 130 | provide autocomplete for all DiscordGo functions. 131 | 132 | ```sh 133 | cd $GOPATH/src/github.com/bwmarrin/discordgo 134 | go install 135 | ``` 136 | 137 | #### Windows 138 | Placeholder. 139 | 140 | 141 | # Next... 142 | More coming soon. 143 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/restapi_test.go: -------------------------------------------------------------------------------- 1 | package discordgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | ////////////////////////////////////////////////////////////////////////////// 8 | /////////////////////////////////////////////////////////////// START OF TESTS 9 | 10 | // TestChannelMessageSend tests the ChannelMessageSend() function. This should not return an error. 11 | func TestChannelMessageSend(t *testing.T) { 12 | 13 | if envChannel == "" { 14 | t.Skip("Skipping, DG_CHANNEL not set.") 15 | } 16 | 17 | if dg == nil { 18 | t.Skip("Skipping, dg not set.") 19 | } 20 | 21 | _, err := dg.ChannelMessageSend(envChannel, "Running REST API Tests!") 22 | if err != nil { 23 | t.Errorf("ChannelMessageSend returned error: %+v", err) 24 | } 25 | } 26 | 27 | /* 28 | // removed for now, only works on BOT accounts now 29 | func TestUserAvatar(t *testing.T) { 30 | 31 | if dg == nil { 32 | t.Skip("Cannot TestUserAvatar, dg not set.") 33 | } 34 | 35 | u, err := dg.User("@me") 36 | if err != nil { 37 | t.Error("error fetching @me user,", err) 38 | } 39 | 40 | a, err := dg.UserAvatar(u.ID) 41 | if err != nil { 42 | if err.Error() == `HTTP 404 NOT FOUND, {"code": 0, "message": "404: Not Found"}` { 43 | t.Skip("Skipped, @me doesn't have an Avatar") 44 | } 45 | t.Errorf(err.Error()) 46 | } 47 | 48 | if a == nil { 49 | t.Errorf("a == nil, should be image.Image") 50 | } 51 | } 52 | */ 53 | 54 | /* Running this causes an error due to 2/hour rate limit on username changes 55 | func TestUserUpdate(t *testing.T) { 56 | if dg == nil { 57 | t.Skip("Cannot test logout, dg not set.") 58 | } 59 | 60 | u, err := dg.User("@me") 61 | if err != nil { 62 | t.Errorf(err.Error()) 63 | } 64 | 65 | s, err := dg.UserUpdate(envEmail, envPassword, "testname", u.Avatar, "") 66 | if err != nil { 67 | t.Error(err.Error()) 68 | } 69 | if s.Username != "testname" { 70 | t.Error("Username != testname") 71 | } 72 | s, err = dg.UserUpdate(envEmail, envPassword, u.Username, u.Avatar, "") 73 | if err != nil { 74 | t.Error(err.Error()) 75 | } 76 | if s.Username != u.Username { 77 | t.Error("Username != " + u.Username) 78 | } 79 | } 80 | */ 81 | 82 | //func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error) { 83 | 84 | func TestUserChannelCreate(t *testing.T) { 85 | if dg == nil { 86 | t.Skip("Cannot TestUserChannelCreate, dg not set.") 87 | } 88 | 89 | if envAdmin == "" { 90 | t.Skip("Skipped, DG_ADMIN not set.") 91 | } 92 | 93 | _, err := dg.UserChannelCreate(envAdmin) 94 | if err != nil { 95 | t.Errorf(err.Error()) 96 | } 97 | 98 | // TODO make sure the channel was added 99 | } 100 | 101 | func TestUserChannels(t *testing.T) { 102 | if dg == nil { 103 | t.Skip("Cannot TestUserChannels, dg not set.") 104 | } 105 | 106 | _, err := dg.UserChannels() 107 | if err != nil { 108 | t.Errorf(err.Error()) 109 | } 110 | } 111 | 112 | func TestUserGuilds(t *testing.T) { 113 | if dg == nil { 114 | t.Skip("Cannot TestUserGuilds, dg not set.") 115 | } 116 | 117 | _, err := dg.UserGuilds(10, "", "") 118 | if err != nil { 119 | t.Errorf(err.Error()) 120 | } 121 | } 122 | 123 | func TestUserSettings(t *testing.T) { 124 | if dg == nil { 125 | t.Skip("Cannot TestUserSettings, dg not set.") 126 | } 127 | 128 | _, err := dg.UserSettings() 129 | if err != nil { 130 | t.Errorf(err.Error()) 131 | } 132 | } 133 | 134 | func TestUserUpdateStatus(t *testing.T) { 135 | if dg == nil { 136 | t.Skip("Cannot TestUserSettings, dg not set.") 137 | } 138 | 139 | _, err := dg.UserUpdateStatus(StatusDoNotDisturb) 140 | if err != nil { 141 | t.Errorf(err.Error()) 142 | } 143 | } 144 | 145 | // TestLogout tests the Logout() function. This should not return an error. 146 | func TestLogout(t *testing.T) { 147 | 148 | if dg == nil { 149 | t.Skip("Cannot TestLogout, dg not set.") 150 | } 151 | 152 | err := dg.Logout() 153 | if err != nil { 154 | t.Errorf("Logout() returned error: %+v", err) 155 | } 156 | } 157 | 158 | func TestGateway(t *testing.T) { 159 | 160 | if dg == nil { 161 | t.Skip("Skipping, dg not set.") 162 | } 163 | _, err := dg.Gateway() 164 | if err != nil { 165 | t.Errorf("Gateway() returned error: %+v", err) 166 | } 167 | } 168 | 169 | func TestGatewayBot(t *testing.T) { 170 | 171 | if dgBot == nil { 172 | t.Skip("Skipping, dgBot not set.") 173 | } 174 | _, err := dgBot.GatewayBot() 175 | if err != nil { 176 | t.Errorf("GatewayBot() returned error: %+v", err) 177 | } 178 | } 179 | 180 | func TestVoiceICE(t *testing.T) { 181 | 182 | if dg == nil { 183 | t.Skip("Skipping, dg not set.") 184 | } 185 | 186 | _, err := dg.VoiceICE() 187 | if err != nil { 188 | t.Errorf("VoiceICE() returned error: %+v", err) 189 | } 190 | } 191 | 192 | func TestVoiceRegions(t *testing.T) { 193 | 194 | if dg == nil { 195 | t.Skip("Skipping, dg not set.") 196 | } 197 | 198 | _, err := dg.VoiceRegions() 199 | if err != nil { 200 | t.Errorf("VoiceRegions() returned error: %+v", err) 201 | } 202 | } 203 | func TestGuildRoles(t *testing.T) { 204 | 205 | if envGuild == "" { 206 | t.Skip("Skipping, DG_GUILD not set.") 207 | } 208 | 209 | if dg == nil { 210 | t.Skip("Skipping, dg not set.") 211 | } 212 | 213 | _, err := dg.GuildRoles(envGuild) 214 | if err != nil { 215 | t.Errorf("GuildRoles(envGuild) returned error: %+v", err) 216 | } 217 | 218 | } 219 | 220 | func TestGuildMemberNickname(t *testing.T) { 221 | 222 | if envGuild == "" { 223 | t.Skip("Skipping, DG_GUILD not set.") 224 | } 225 | 226 | if dg == nil { 227 | t.Skip("Skipping, dg not set.") 228 | } 229 | 230 | err := dg.GuildMemberNickname(envGuild, "@me/nick", "testnickname") 231 | if err != nil { 232 | t.Errorf("GuildNickname returned error: %+v", err) 233 | } 234 | } 235 | 236 | // TestChannelMessageSend2 tests the ChannelMessageSend() function. This should not return an error. 237 | func TestChannelMessageSend2(t *testing.T) { 238 | 239 | if envChannel == "" { 240 | t.Skip("Skipping, DG_CHANNEL not set.") 241 | } 242 | 243 | if dg == nil { 244 | t.Skip("Skipping, dg not set.") 245 | } 246 | 247 | _, err := dg.ChannelMessageSend(envChannel, "All done running REST API Tests!") 248 | if err != nil { 249 | t.Errorf("ChannelMessageSend returned error: %+v", err) 250 | } 251 | } 252 | 253 | // TestGuildPruneCount tests GuildPruneCount() function. This should not return an error. 254 | func TestGuildPruneCount(t *testing.T) { 255 | 256 | if envGuild == "" { 257 | t.Skip("Skipping, DG_GUILD not set.") 258 | } 259 | 260 | if dg == nil { 261 | t.Skip("Skipping, dg not set.") 262 | } 263 | 264 | _, err := dg.GuildPruneCount(envGuild, 1) 265 | if err != nil { 266 | t.Errorf("GuildPruneCount returned error: %+v", err) 267 | } 268 | } 269 | 270 | /* 271 | // TestGuildPrune tests GuildPrune() function. This should not return an error. 272 | func TestGuildPrune(t *testing.T) { 273 | 274 | if envGuild == "" { 275 | t.Skip("Skipping, DG_GUILD not set.") 276 | } 277 | 278 | if dg == nil { 279 | t.Skip("Skipping, dg not set.") 280 | } 281 | 282 | _, err := dg.GuildPrune(envGuild, 1) 283 | if err != nil { 284 | t.Errorf("GuildPrune returned error: %+v", err) 285 | } 286 | } 287 | */ 288 | -------------------------------------------------------------------------------- /vendor/github.com/matryer/moq/pkg/moq/moq_test.go: -------------------------------------------------------------------------------- 1 | package moq 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestMoq(t *testing.T) { 11 | m, err := New("testpackages/example", "") 12 | if err != nil { 13 | t.Fatalf("moq.New: %s", err) 14 | } 15 | var buf bytes.Buffer 16 | err = m.Mock(&buf, "PersonStore") 17 | if err != nil { 18 | t.Errorf("m.Mock: %s", err) 19 | } 20 | s := buf.String() 21 | // assertions of things that should be mentioned 22 | var strs = []string{ 23 | "package example", 24 | "type PersonStoreMock struct", 25 | "CreateFunc func(ctx context.Context, person *Person, confirm bool) error", 26 | "GetFunc func(ctx context.Context, id string) (*Person, error)", 27 | "func (mock *PersonStoreMock) Create(ctx context.Context, person *Person, confirm bool) error", 28 | "func (mock *PersonStoreMock) Get(ctx context.Context, id string) (*Person, error)", 29 | "panic(\"moq: PersonStoreMock.CreateFunc is nil but PersonStore.Create was just called\")", 30 | "panic(\"moq: PersonStoreMock.GetFunc is nil but PersonStore.Get was just called\")", 31 | "lockPersonStoreMockGet.Lock()", 32 | "mock.calls.Get = append(mock.calls.Get, callInfo)", 33 | "lockPersonStoreMockGet.Unlock()", 34 | } 35 | for _, str := range strs { 36 | if !strings.Contains(s, str) { 37 | t.Errorf("expected but missing: \"%s\"", str) 38 | } 39 | } 40 | } 41 | 42 | func TestMoqExplicitPackage(t *testing.T) { 43 | m, err := New("testpackages/example", "different") 44 | if err != nil { 45 | t.Fatalf("moq.New: %s", err) 46 | } 47 | var buf bytes.Buffer 48 | err = m.Mock(&buf, "PersonStore") 49 | if err != nil { 50 | t.Errorf("m.Mock: %s", err) 51 | } 52 | s := buf.String() 53 | // assertions of things that should be mentioned 54 | var strs = []string{ 55 | "package different", 56 | "type PersonStoreMock struct", 57 | "CreateFunc func(ctx context.Context, person *example.Person, confirm bool) error", 58 | "GetFunc func(ctx context.Context, id string) (*example.Person, error)", 59 | "func (mock *PersonStoreMock) Create(ctx context.Context, person *example.Person, confirm bool) error", 60 | "func (mock *PersonStoreMock) Get(ctx context.Context, id string) (*example.Person, error)", 61 | } 62 | for _, str := range strs { 63 | if !strings.Contains(s, str) { 64 | t.Errorf("expected but missing: \"%s\"", str) 65 | } 66 | } 67 | } 68 | 69 | // TestVeradicArguments tests to ensure variadic work as 70 | // expected. 71 | // see https://github.com/matryer/moq/issues/5 72 | func TestVariadicArguments(t *testing.T) { 73 | m, err := New("testpackages/variadic", "") 74 | if err != nil { 75 | t.Fatalf("moq.New: %s", err) 76 | } 77 | var buf bytes.Buffer 78 | err = m.Mock(&buf, "Greeter") 79 | if err != nil { 80 | t.Errorf("m.Mock: %s", err) 81 | } 82 | s := buf.String() 83 | // assertions of things that should be mentioned 84 | var strs = []string{ 85 | "package variadic", 86 | "type GreeterMock struct", 87 | "GreetFunc func(ctx context.Context, names ...string) string", 88 | "return mock.GreetFunc(ctx, names...)", 89 | } 90 | for _, str := range strs { 91 | if !strings.Contains(s, str) { 92 | t.Errorf("expected but missing: \"%s\"", str) 93 | } 94 | } 95 | } 96 | 97 | func TestNothingToReturn(t *testing.T) { 98 | m, err := New("testpackages/example", "") 99 | if err != nil { 100 | t.Fatalf("moq.New: %s", err) 101 | } 102 | var buf bytes.Buffer 103 | err = m.Mock(&buf, "PersonStore") 104 | if err != nil { 105 | t.Errorf("m.Mock: %s", err) 106 | } 107 | s := buf.String() 108 | if strings.Contains(s, `return mock.ClearCacheFunc(id)`) { 109 | t.Errorf("should not have return for items that have no return arguments") 110 | } 111 | // assertions of things that should be mentioned 112 | var strs = []string{ 113 | "mock.ClearCacheFunc(id)", 114 | } 115 | for _, str := range strs { 116 | if !strings.Contains(s, str) { 117 | t.Errorf("expected but missing: \"%s\"", str) 118 | } 119 | } 120 | } 121 | 122 | func TestChannelNames(t *testing.T) { 123 | m, err := New("testpackages/channels", "") 124 | if err != nil { 125 | t.Fatalf("moq.New: %s", err) 126 | } 127 | var buf bytes.Buffer 128 | err = m.Mock(&buf, "Queuer") 129 | if err != nil { 130 | t.Errorf("m.Mock: %s", err) 131 | } 132 | s := buf.String() 133 | var strs = []string{ 134 | "func (mock *QueuerMock) Sub(topic string) (<-chan Queue, error)", 135 | } 136 | for _, str := range strs { 137 | if !strings.Contains(s, str) { 138 | t.Errorf("expected but missing: \"%s\"", str) 139 | } 140 | } 141 | } 142 | 143 | func TestImports(t *testing.T) { 144 | m, err := New("testpackages/imports/two", "") 145 | if err != nil { 146 | t.Fatalf("moq.New: %s", err) 147 | } 148 | var buf bytes.Buffer 149 | err = m.Mock(&buf, "DoSomething") 150 | if err != nil { 151 | t.Errorf("m.Mock: %s", err) 152 | } 153 | s := buf.String() 154 | var strs = []string{ 155 | ` "sync"`, 156 | ` "github.com/matryer/moq/pkg/moq/testpackages/imports/one"`, 157 | } 158 | for _, str := range strs { 159 | if !strings.Contains(s, str) { 160 | t.Errorf("expected but missing: \"%s\"", str) 161 | } 162 | if len(strings.Split(s, str)) > 2 { 163 | t.Errorf("more than one: \"%s\"", str) 164 | } 165 | } 166 | } 167 | 168 | func TestTemplateFuncs(t *testing.T) { 169 | fn := templateFuncs["Exported"].(func(string) string) 170 | if fn("var") != "Var" { 171 | t.Errorf("exported didn't work: %s", fn("var")) 172 | } 173 | } 174 | 175 | func TestVendoredPackages(t *testing.T) { 176 | m, err := New("testpackages/vendoring/user", "") 177 | if err != nil { 178 | t.Fatalf("moq.New: %s", err) 179 | } 180 | var buf bytes.Buffer 181 | err = m.Mock(&buf, "Service") 182 | if err != nil { 183 | t.Errorf("mock error: %s", err) 184 | } 185 | s := buf.String() 186 | // assertions of things that should be mentioned 187 | var strs = []string{ 188 | `"github.com/matryer/somerepo"`, 189 | } 190 | for _, str := range strs { 191 | if !strings.Contains(s, str) { 192 | t.Errorf("expected but missing: \"%s\"", str) 193 | } 194 | } 195 | } 196 | 197 | // TestDotImports tests for https://github.com/matryer/moq/issues/21. 198 | func TestDotImports(t *testing.T) { 199 | preDir, err := os.Getwd() 200 | if err != nil { 201 | t.Errorf("Getwd: %s", err) 202 | } 203 | err = os.Chdir("testpackages/dotimport") 204 | if err != nil { 205 | t.Errorf("Chdir: %s", err) 206 | } 207 | defer func() { 208 | err := os.Chdir(preDir) 209 | if err != nil { 210 | t.Errorf("Chdir back: %s", err) 211 | } 212 | }() 213 | m, err := New(".", "moqtest_test") 214 | if err != nil { 215 | t.Fatalf("moq.New: %s", err) 216 | } 217 | var buf bytes.Buffer 218 | err = m.Mock(&buf, "Service") 219 | if err != nil { 220 | t.Errorf("mock error: %s", err) 221 | } 222 | s := buf.String() 223 | if !strings.Contains(s, `/moq/pkg/moq/testpackages/dotimport"`) { 224 | t.Error("contains invalid dot import") 225 | } 226 | } 227 | 228 | func TestEmptyInterface(t *testing.T) { 229 | m, err := New("testpackages/emptyinterface", "") 230 | if err != nil { 231 | t.Fatalf("moq.New: %s", err) 232 | } 233 | var buf bytes.Buffer 234 | err = m.Mock(&buf, "Empty") 235 | if err != nil { 236 | t.Errorf("mock error: %s", err) 237 | } 238 | s := buf.String() 239 | if strings.Contains(s, `"sync"`) { 240 | t.Error("contains sync import, although this package isn't used") 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/events.go: -------------------------------------------------------------------------------- 1 | package discordgo 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // This file contains all the possible structs that can be 8 | // handled by AddHandler/EventHandler. 9 | // DO NOT ADD ANYTHING BUT EVENT HANDLER STRUCTS TO THIS FILE. 10 | //go:generate go run tools/cmd/eventhandlers/main.go 11 | 12 | // Connect is the data for a Connect event. 13 | // This is a sythetic event and is not dispatched by Discord. 14 | type Connect struct{} 15 | 16 | // Disconnect is the data for a Disconnect event. 17 | // This is a sythetic event and is not dispatched by Discord. 18 | type Disconnect struct{} 19 | 20 | // RateLimit is the data for a RateLimit event. 21 | // This is a sythetic event and is not dispatched by Discord. 22 | type RateLimit struct { 23 | *TooManyRequests 24 | URL string 25 | } 26 | 27 | // Event provides a basic initial struct for all websocket events. 28 | type Event struct { 29 | Operation int `json:"op"` 30 | Sequence int64 `json:"s"` 31 | Type string `json:"t"` 32 | RawData json.RawMessage `json:"d"` 33 | // Struct contains one of the other types in this file. 34 | Struct interface{} `json:"-"` 35 | } 36 | 37 | // A Ready stores all data for the websocket READY event. 38 | type Ready struct { 39 | Version int `json:"v"` 40 | SessionID string `json:"session_id"` 41 | User *User `json:"user"` 42 | ReadState []*ReadState `json:"read_state"` 43 | PrivateChannels []*Channel `json:"private_channels"` 44 | Guilds []*Guild `json:"guilds"` 45 | 46 | // Undocumented fields 47 | Settings *Settings `json:"user_settings"` 48 | UserGuildSettings []*UserGuildSettings `json:"user_guild_settings"` 49 | Relationships []*Relationship `json:"relationships"` 50 | Presences []*Presence `json:"presences"` 51 | Notes map[string]string `json:"notes"` 52 | } 53 | 54 | // ChannelCreate is the data for a ChannelCreate event. 55 | type ChannelCreate struct { 56 | *Channel 57 | } 58 | 59 | // ChannelUpdate is the data for a ChannelUpdate event. 60 | type ChannelUpdate struct { 61 | *Channel 62 | } 63 | 64 | // ChannelDelete is the data for a ChannelDelete event. 65 | type ChannelDelete struct { 66 | *Channel 67 | } 68 | 69 | // ChannelPinsUpdate stores data for a ChannelPinsUpdate event. 70 | type ChannelPinsUpdate struct { 71 | LastPinTimestamp string `json:"last_pin_timestamp"` 72 | ChannelID string `json:"channel_id"` 73 | } 74 | 75 | // GuildCreate is the data for a GuildCreate event. 76 | type GuildCreate struct { 77 | *Guild 78 | } 79 | 80 | // GuildUpdate is the data for a GuildUpdate event. 81 | type GuildUpdate struct { 82 | *Guild 83 | } 84 | 85 | // GuildDelete is the data for a GuildDelete event. 86 | type GuildDelete struct { 87 | *Guild 88 | } 89 | 90 | // GuildBanAdd is the data for a GuildBanAdd event. 91 | type GuildBanAdd struct { 92 | User *User `json:"user"` 93 | GuildID string `json:"guild_id"` 94 | } 95 | 96 | // GuildBanRemove is the data for a GuildBanRemove event. 97 | type GuildBanRemove struct { 98 | User *User `json:"user"` 99 | GuildID string `json:"guild_id"` 100 | } 101 | 102 | // GuildMemberAdd is the data for a GuildMemberAdd event. 103 | type GuildMemberAdd struct { 104 | *Member 105 | } 106 | 107 | // GuildMemberUpdate is the data for a GuildMemberUpdate event. 108 | type GuildMemberUpdate struct { 109 | *Member 110 | } 111 | 112 | // GuildMemberRemove is the data for a GuildMemberRemove event. 113 | type GuildMemberRemove struct { 114 | *Member 115 | } 116 | 117 | // GuildRoleCreate is the data for a GuildRoleCreate event. 118 | type GuildRoleCreate struct { 119 | *GuildRole 120 | } 121 | 122 | // GuildRoleUpdate is the data for a GuildRoleUpdate event. 123 | type GuildRoleUpdate struct { 124 | *GuildRole 125 | } 126 | 127 | // A GuildRoleDelete is the data for a GuildRoleDelete event. 128 | type GuildRoleDelete struct { 129 | RoleID string `json:"role_id"` 130 | GuildID string `json:"guild_id"` 131 | } 132 | 133 | // A GuildEmojisUpdate is the data for a guild emoji update event. 134 | type GuildEmojisUpdate struct { 135 | GuildID string `json:"guild_id"` 136 | Emojis []*Emoji `json:"emojis"` 137 | } 138 | 139 | // A GuildMembersChunk is the data for a GuildMembersChunk event. 140 | type GuildMembersChunk struct { 141 | GuildID string `json:"guild_id"` 142 | Members []*Member `json:"members"` 143 | } 144 | 145 | // GuildIntegrationsUpdate is the data for a GuildIntegrationsUpdate event. 146 | type GuildIntegrationsUpdate struct { 147 | GuildID string `json:"guild_id"` 148 | } 149 | 150 | // MessageAck is the data for a MessageAck event. 151 | type MessageAck struct { 152 | MessageID string `json:"message_id"` 153 | ChannelID string `json:"channel_id"` 154 | } 155 | 156 | // MessageCreate is the data for a MessageCreate event. 157 | type MessageCreate struct { 158 | *Message 159 | } 160 | 161 | // MessageUpdate is the data for a MessageUpdate event. 162 | type MessageUpdate struct { 163 | *Message 164 | } 165 | 166 | // MessageDelete is the data for a MessageDelete event. 167 | type MessageDelete struct { 168 | *Message 169 | } 170 | 171 | // MessageReactionAdd is the data for a MessageReactionAdd event. 172 | type MessageReactionAdd struct { 173 | *MessageReaction 174 | } 175 | 176 | // MessageReactionRemove is the data for a MessageReactionRemove event. 177 | type MessageReactionRemove struct { 178 | *MessageReaction 179 | } 180 | 181 | // MessageReactionRemoveAll is the data for a MessageReactionRemoveAll event. 182 | type MessageReactionRemoveAll struct { 183 | *MessageReaction 184 | } 185 | 186 | // PresencesReplace is the data for a PresencesReplace event. 187 | type PresencesReplace []*Presence 188 | 189 | // PresenceUpdate is the data for a PresenceUpdate event. 190 | type PresenceUpdate struct { 191 | Presence 192 | GuildID string `json:"guild_id"` 193 | Roles []string `json:"roles"` 194 | } 195 | 196 | // Resumed is the data for a Resumed event. 197 | type Resumed struct { 198 | Trace []string `json:"_trace"` 199 | } 200 | 201 | // RelationshipAdd is the data for a RelationshipAdd event. 202 | type RelationshipAdd struct { 203 | *Relationship 204 | } 205 | 206 | // RelationshipRemove is the data for a RelationshipRemove event. 207 | type RelationshipRemove struct { 208 | *Relationship 209 | } 210 | 211 | // TypingStart is the data for a TypingStart event. 212 | type TypingStart struct { 213 | UserID string `json:"user_id"` 214 | ChannelID string `json:"channel_id"` 215 | Timestamp int `json:"timestamp"` 216 | } 217 | 218 | // UserUpdate is the data for a UserUpdate event. 219 | type UserUpdate struct { 220 | *User 221 | } 222 | 223 | // UserSettingsUpdate is the data for a UserSettingsUpdate event. 224 | type UserSettingsUpdate map[string]interface{} 225 | 226 | // UserGuildSettingsUpdate is the data for a UserGuildSettingsUpdate event. 227 | type UserGuildSettingsUpdate struct { 228 | *UserGuildSettings 229 | } 230 | 231 | // UserNoteUpdate is the data for a UserNoteUpdate event. 232 | type UserNoteUpdate struct { 233 | ID string `json:"id"` 234 | Note string `json:"note"` 235 | } 236 | 237 | // VoiceServerUpdate is the data for a VoiceServerUpdate event. 238 | type VoiceServerUpdate struct { 239 | Token string `json:"token"` 240 | GuildID string `json:"guild_id"` 241 | Endpoint string `json:"endpoint"` 242 | } 243 | 244 | // VoiceStateUpdate is the data for a VoiceStateUpdate event. 245 | type VoiceStateUpdate struct { 246 | *VoiceState 247 | } 248 | 249 | // MessageDeleteBulk is the data for a MessageDeleteBulk event 250 | type MessageDeleteBulk struct { 251 | Messages []string `json:"ids"` 252 | ChannelID string `json:"channel_id"` 253 | } 254 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/event.go: -------------------------------------------------------------------------------- 1 | package discordgo 2 | 3 | // EventHandler is an interface for Discord events. 4 | type EventHandler interface { 5 | // Type returns the type of event this handler belongs to. 6 | Type() string 7 | 8 | // Handle is called whenever an event of Type() happens. 9 | // It is the recievers responsibility to type assert that the interface 10 | // is the expected struct. 11 | Handle(*Session, interface{}) 12 | } 13 | 14 | // EventInterfaceProvider is an interface for providing empty interfaces for 15 | // Discord events. 16 | type EventInterfaceProvider interface { 17 | // Type is the type of event this handler belongs to. 18 | Type() string 19 | 20 | // New returns a new instance of the struct this event handler handles. 21 | // This is called once per event. 22 | // The struct is provided to all handlers of the same Type(). 23 | New() interface{} 24 | } 25 | 26 | // interfaceEventType is the event handler type for interface{} events. 27 | const interfaceEventType = "__INTERFACE__" 28 | 29 | // interfaceEventHandler is an event handler for interface{} events. 30 | type interfaceEventHandler func(*Session, interface{}) 31 | 32 | // Type returns the event type for interface{} events. 33 | func (eh interfaceEventHandler) Type() string { 34 | return interfaceEventType 35 | } 36 | 37 | // Handle is the handler for an interface{} event. 38 | func (eh interfaceEventHandler) Handle(s *Session, i interface{}) { 39 | eh(s, i) 40 | } 41 | 42 | var registeredInterfaceProviders = map[string]EventInterfaceProvider{} 43 | 44 | // registerInterfaceProvider registers a provider so that DiscordGo can 45 | // access it's New() method. 46 | func registerInterfaceProvider(eh EventInterfaceProvider) { 47 | if _, ok := registeredInterfaceProviders[eh.Type()]; ok { 48 | return 49 | // XXX: 50 | // if we should error here, we need to do something with it. 51 | // fmt.Errorf("event %s already registered", eh.Type()) 52 | } 53 | registeredInterfaceProviders[eh.Type()] = eh 54 | return 55 | } 56 | 57 | // eventHandlerInstance is a wrapper around an event handler, as functions 58 | // cannot be compared directly. 59 | type eventHandlerInstance struct { 60 | eventHandler EventHandler 61 | } 62 | 63 | // addEventHandler adds an event handler that will be fired anytime 64 | // the Discord WSAPI matching eventHandler.Type() fires. 65 | func (s *Session) addEventHandler(eventHandler EventHandler) func() { 66 | s.handlersMu.Lock() 67 | defer s.handlersMu.Unlock() 68 | 69 | if s.handlers == nil { 70 | s.handlers = map[string][]*eventHandlerInstance{} 71 | } 72 | 73 | ehi := &eventHandlerInstance{eventHandler} 74 | s.handlers[eventHandler.Type()] = append(s.handlers[eventHandler.Type()], ehi) 75 | 76 | return func() { 77 | s.removeEventHandlerInstance(eventHandler.Type(), ehi) 78 | } 79 | } 80 | 81 | // addEventHandler adds an event handler that will be fired the next time 82 | // the Discord WSAPI matching eventHandler.Type() fires. 83 | func (s *Session) addEventHandlerOnce(eventHandler EventHandler) func() { 84 | s.handlersMu.Lock() 85 | defer s.handlersMu.Unlock() 86 | 87 | if s.onceHandlers == nil { 88 | s.onceHandlers = map[string][]*eventHandlerInstance{} 89 | } 90 | 91 | ehi := &eventHandlerInstance{eventHandler} 92 | s.onceHandlers[eventHandler.Type()] = append(s.onceHandlers[eventHandler.Type()], ehi) 93 | 94 | return func() { 95 | s.removeEventHandlerInstance(eventHandler.Type(), ehi) 96 | } 97 | } 98 | 99 | // AddHandler allows you to add an event handler that will be fired anytime 100 | // the Discord WSAPI event that matches the function fires. 101 | // events.go contains all the Discord WSAPI events that can be fired. 102 | // eg: 103 | // Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { 104 | // }) 105 | // 106 | // or: 107 | // Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) { 108 | // }) 109 | // The return value of this method is a function, that when called will remove the 110 | // event handler. 111 | func (s *Session) AddHandler(handler interface{}) func() { 112 | eh := handlerForInterface(handler) 113 | 114 | if eh == nil { 115 | s.log(LogError, "Invalid handler type, handler will never be called") 116 | return func() {} 117 | } 118 | 119 | return s.addEventHandler(eh) 120 | } 121 | 122 | // AddHandlerOnce allows you to add an event handler that will be fired the next time 123 | // the Discord WSAPI event that matches the function fires. 124 | // See AddHandler for more details. 125 | func (s *Session) AddHandlerOnce(handler interface{}) func() { 126 | eh := handlerForInterface(handler) 127 | 128 | if eh == nil { 129 | s.log(LogError, "Invalid handler type, handler will never be called") 130 | return func() {} 131 | } 132 | 133 | return s.addEventHandlerOnce(eh) 134 | } 135 | 136 | // removeEventHandler instance removes an event handler instance. 137 | func (s *Session) removeEventHandlerInstance(t string, ehi *eventHandlerInstance) { 138 | s.handlersMu.Lock() 139 | defer s.handlersMu.Unlock() 140 | 141 | handlers := s.handlers[t] 142 | for i := range handlers { 143 | if handlers[i] == ehi { 144 | s.handlers[t] = append(handlers[:i], handlers[i+1:]...) 145 | } 146 | } 147 | 148 | onceHandlers := s.onceHandlers[t] 149 | for i := range onceHandlers { 150 | if onceHandlers[i] == ehi { 151 | s.onceHandlers[t] = append(onceHandlers[:i], handlers[i+1:]...) 152 | } 153 | } 154 | } 155 | 156 | // Handles calling permanent and once handlers for an event type. 157 | func (s *Session) handle(t string, i interface{}) { 158 | for _, eh := range s.handlers[t] { 159 | if s.SyncEvents { 160 | eh.eventHandler.Handle(s, i) 161 | } else { 162 | go eh.eventHandler.Handle(s, i) 163 | } 164 | } 165 | 166 | if len(s.onceHandlers[t]) > 0 { 167 | for _, eh := range s.onceHandlers[t] { 168 | if s.SyncEvents { 169 | eh.eventHandler.Handle(s, i) 170 | } else { 171 | go eh.eventHandler.Handle(s, i) 172 | } 173 | } 174 | s.onceHandlers[t] = nil 175 | } 176 | } 177 | 178 | // Handles an event type by calling internal methods, firing handlers and firing the 179 | // interface{} event. 180 | func (s *Session) handleEvent(t string, i interface{}) { 181 | s.handlersMu.RLock() 182 | defer s.handlersMu.RUnlock() 183 | 184 | // All events are dispatched internally first. 185 | s.onInterface(i) 186 | 187 | // Then they are dispatched to anyone handling interface{} events. 188 | s.handle(interfaceEventType, i) 189 | 190 | // Finally they are dispatched to any typed handlers. 191 | s.handle(t, i) 192 | } 193 | 194 | // setGuildIds will set the GuildID on all the members of a guild. 195 | // This is done as event data does not have it set. 196 | func setGuildIds(g *Guild) { 197 | for _, c := range g.Channels { 198 | c.GuildID = g.ID 199 | } 200 | 201 | for _, m := range g.Members { 202 | m.GuildID = g.ID 203 | } 204 | 205 | for _, vs := range g.VoiceStates { 206 | vs.GuildID = g.ID 207 | } 208 | } 209 | 210 | // onInterface handles all internal events and routes them to the appropriate internal handler. 211 | func (s *Session) onInterface(i interface{}) { 212 | switch t := i.(type) { 213 | case *Ready: 214 | for _, g := range t.Guilds { 215 | setGuildIds(g) 216 | } 217 | s.onReady(t) 218 | case *GuildCreate: 219 | setGuildIds(t.Guild) 220 | case *GuildUpdate: 221 | setGuildIds(t.Guild) 222 | case *VoiceServerUpdate: 223 | go s.onVoiceServerUpdate(t) 224 | case *VoiceStateUpdate: 225 | go s.onVoiceStateUpdate(t) 226 | } 227 | err := s.State.OnInterface(s, i) 228 | if err != nil { 229 | s.log(LogDebug, "error dispatching internal event, %s", err) 230 | } 231 | } 232 | 233 | // onReady handles the ready event. 234 | func (s *Session) onReady(r *Ready) { 235 | 236 | // Store the SessionID within the Session struct. 237 | s.sessionID = r.SessionID 238 | } 239 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/discord_test.go: -------------------------------------------------------------------------------- 1 | package discordgo 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "sync/atomic" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | ////////////////////////////////////////////////////////////////////////////// 12 | ////////////////////////////////////////////////////// VARS NEEDED FOR TESTING 13 | var ( 14 | dg *Session // Stores a global discordgo user session 15 | dgBot *Session // Stores a global discordgo bot session 16 | 17 | envToken = os.Getenv("DG_TOKEN") // Token to use when authenticating the user account 18 | envBotToken = os.Getenv("DGB_TOKEN") // Token to use when authenticating the bot account 19 | envEmail = os.Getenv("DG_EMAIL") // Email to use when authenticating 20 | envPassword = os.Getenv("DG_PASSWORD") // Password to use when authenticating 21 | envGuild = os.Getenv("DG_GUILD") // Guild ID to use for tests 22 | envChannel = os.Getenv("DG_CHANNEL") // Channel ID to use for tests 23 | // envUser = os.Getenv("DG_USER") // User ID to use for tests 24 | envAdmin = os.Getenv("DG_ADMIN") // User ID of admin user to use for tests 25 | ) 26 | 27 | func init() { 28 | if envBotToken != "" { 29 | if d, err := New(envBotToken); err == nil { 30 | dgBot = d 31 | } 32 | } 33 | 34 | if envEmail == "" || envPassword == "" || envToken == "" { 35 | return 36 | } 37 | 38 | if d, err := New(envEmail, envPassword, envToken); err == nil { 39 | dg = d 40 | } 41 | } 42 | 43 | ////////////////////////////////////////////////////////////////////////////// 44 | /////////////////////////////////////////////////////////////// START OF TESTS 45 | 46 | // TestNew tests the New() function without any arguments. This should return 47 | // a valid Session{} struct and no errors. 48 | func TestNew(t *testing.T) { 49 | 50 | _, err := New() 51 | if err != nil { 52 | t.Errorf("New() returned error: %+v", err) 53 | } 54 | } 55 | 56 | // TestInvalidToken tests the New() function with an invalid token 57 | func TestInvalidToken(t *testing.T) { 58 | d, err := New("asjkldhflkjasdh") 59 | if err != nil { 60 | t.Fatalf("New(InvalidToken) returned error: %+v", err) 61 | } 62 | 63 | // New with just a token does not do any communication, so attempt an api call. 64 | _, err = d.UserSettings() 65 | if err == nil { 66 | t.Errorf("New(InvalidToken), d.UserSettings returned nil error.") 67 | } 68 | } 69 | 70 | // TestInvalidUserPass tests the New() function with an invalid Email and Pass 71 | func TestInvalidEmailPass(t *testing.T) { 72 | 73 | _, err := New("invalidemail", "invalidpassword") 74 | if err == nil { 75 | t.Errorf("New(InvalidEmail, InvalidPass) returned nil error.") 76 | } 77 | 78 | } 79 | 80 | // TestInvalidPass tests the New() function with an invalid Password 81 | func TestInvalidPass(t *testing.T) { 82 | 83 | if envEmail == "" { 84 | t.Skip("Skipping New(username,InvalidPass), DG_EMAIL not set") 85 | return 86 | } 87 | _, err := New(envEmail, "invalidpassword") 88 | if err == nil { 89 | t.Errorf("New(Email, InvalidPass) returned nil error.") 90 | } 91 | } 92 | 93 | // TestNewUserPass tests the New() function with a username and password. 94 | // This should return a valid Session{}, a valid Session.Token. 95 | func TestNewUserPass(t *testing.T) { 96 | 97 | if envEmail == "" || envPassword == "" { 98 | t.Skip("Skipping New(username,password), DG_EMAIL or DG_PASSWORD not set") 99 | return 100 | } 101 | 102 | d, err := New(envEmail, envPassword) 103 | if err != nil { 104 | t.Fatalf("New(user,pass) returned error: %+v", err) 105 | } 106 | 107 | if d == nil { 108 | t.Fatal("New(user,pass), d is nil, should be Session{}") 109 | } 110 | 111 | if d.Token == "" { 112 | t.Fatal("New(user,pass), d.Token is empty, should be a valid Token.") 113 | } 114 | } 115 | 116 | // TestNewToken tests the New() function with a Token. This should return 117 | // the same as the TestNewUserPass function. 118 | func TestNewToken(t *testing.T) { 119 | 120 | if envToken == "" { 121 | t.Skip("Skipping New(token), DG_TOKEN not set") 122 | } 123 | 124 | d, err := New(envToken) 125 | if err != nil { 126 | t.Fatalf("New(envToken) returned error: %+v", err) 127 | } 128 | 129 | if d == nil { 130 | t.Fatal("New(envToken), d is nil, should be Session{}") 131 | } 132 | 133 | if d.Token == "" { 134 | t.Fatal("New(envToken), d.Token is empty, should be a valid Token.") 135 | } 136 | } 137 | 138 | // TestNewUserPassToken tests the New() function with a username, password and token. 139 | // This should return the same as the TestNewUserPass function. 140 | func TestNewUserPassToken(t *testing.T) { 141 | 142 | if envEmail == "" || envPassword == "" || envToken == "" { 143 | t.Skip("Skipping New(username,password,token), DG_EMAIL, DG_PASSWORD or DG_TOKEN not set") 144 | return 145 | } 146 | 147 | d, err := New(envEmail, envPassword, envToken) 148 | if err != nil { 149 | t.Fatalf("New(user,pass,token) returned error: %+v", err) 150 | } 151 | 152 | if d == nil { 153 | t.Fatal("New(user,pass,token), d is nil, should be Session{}") 154 | } 155 | 156 | if d.Token == "" { 157 | t.Fatal("New(user,pass,token), d.Token is empty, should be a valid Token.") 158 | } 159 | } 160 | 161 | func TestOpenClose(t *testing.T) { 162 | if envToken == "" { 163 | t.Skip("Skipping TestClose, DG_TOKEN not set") 164 | } 165 | 166 | d, err := New(envToken) 167 | if err != nil { 168 | t.Fatalf("TestClose, New(envToken) returned error: %+v", err) 169 | } 170 | 171 | if err = d.Open(); err != nil { 172 | t.Fatalf("TestClose, d.Open failed: %+v", err) 173 | } 174 | 175 | // We need a better way to know the session is ready for use, 176 | // this is totally gross. 177 | start := time.Now() 178 | for { 179 | d.RLock() 180 | if d.DataReady { 181 | d.RUnlock() 182 | break 183 | } 184 | d.RUnlock() 185 | 186 | if time.Since(start) > 10*time.Second { 187 | t.Fatal("DataReady never became true.yy") 188 | } 189 | runtime.Gosched() 190 | } 191 | 192 | // TODO find a better way 193 | // Add a small sleep here to make sure heartbeat and other events 194 | // have enough time to get fired. Need a way to actually check 195 | // those events. 196 | time.Sleep(2 * time.Second) 197 | 198 | // UpdateStatus - maybe we move this into wsapi_test.go but the websocket 199 | // created here is needed. This helps tests that the websocket was setup 200 | // and it is working. 201 | if err = d.UpdateStatus(0, time.Now().String()); err != nil { 202 | t.Errorf("UpdateStatus error: %+v", err) 203 | } 204 | 205 | if err = d.Close(); err != nil { 206 | t.Fatalf("TestClose, d.Close failed: %+v", err) 207 | } 208 | } 209 | 210 | func TestAddHandler(t *testing.T) { 211 | 212 | testHandlerCalled := int32(0) 213 | testHandler := func(s *Session, m *MessageCreate) { 214 | atomic.AddInt32(&testHandlerCalled, 1) 215 | } 216 | 217 | interfaceHandlerCalled := int32(0) 218 | interfaceHandler := func(s *Session, i interface{}) { 219 | atomic.AddInt32(&interfaceHandlerCalled, 1) 220 | } 221 | 222 | bogusHandlerCalled := int32(0) 223 | bogusHandler := func(s *Session, se *Session) { 224 | atomic.AddInt32(&bogusHandlerCalled, 1) 225 | } 226 | 227 | d := Session{} 228 | d.AddHandler(testHandler) 229 | d.AddHandler(testHandler) 230 | 231 | d.AddHandler(interfaceHandler) 232 | d.AddHandler(bogusHandler) 233 | 234 | d.handleEvent(messageCreateEventType, &MessageCreate{}) 235 | d.handleEvent(messageDeleteEventType, &MessageDelete{}) 236 | 237 | <-time.After(500 * time.Millisecond) 238 | 239 | // testHandler will be called twice because it was added twice. 240 | if atomic.LoadInt32(&testHandlerCalled) != 2 { 241 | t.Fatalf("testHandler was not called twice.") 242 | } 243 | 244 | // interfaceHandler will be called twice, once for each event. 245 | if atomic.LoadInt32(&interfaceHandlerCalled) != 2 { 246 | t.Fatalf("interfaceHandler was not called twice.") 247 | } 248 | 249 | if atomic.LoadInt32(&bogusHandlerCalled) != 0 { 250 | t.Fatalf("bogusHandler was called.") 251 | } 252 | } 253 | 254 | func TestRemoveHandler(t *testing.T) { 255 | 256 | testHandlerCalled := int32(0) 257 | testHandler := func(s *Session, m *MessageCreate) { 258 | atomic.AddInt32(&testHandlerCalled, 1) 259 | } 260 | 261 | d := Session{} 262 | r := d.AddHandler(testHandler) 263 | 264 | d.handleEvent(messageCreateEventType, &MessageCreate{}) 265 | 266 | r() 267 | 268 | d.handleEvent(messageCreateEventType, &MessageCreate{}) 269 | 270 | <-time.After(500 * time.Millisecond) 271 | 272 | // testHandler will be called once, as it was removed in between calls. 273 | if atomic.LoadInt32(&testHandlerCalled) != 1 { 274 | t.Fatalf("testHandler was not called once.") 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/message.go: -------------------------------------------------------------------------------- 1 | // Discordgo - Discord bindings for Go 2 | // Available at https://github.com/bwmarrin/discordgo 3 | 4 | // Copyright 2015-2016 Bruce Marriner . All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | // This file contains code related to the Message struct 9 | 10 | package discordgo 11 | 12 | import ( 13 | "io" 14 | "regexp" 15 | "strings" 16 | ) 17 | 18 | // MessageType is the type of Message 19 | type MessageType int 20 | 21 | // Block contains the valid known MessageType values 22 | const ( 23 | MessageTypeDefault MessageType = iota 24 | MessageTypeRecipientAdd 25 | MessageTypeRecipientRemove 26 | MessageTypeCall 27 | MessageTypeChannelNameChange 28 | MessageTypeChannelIconChange 29 | MessageTypeChannelPinnedMessage 30 | MessageTypeGuildMemberJoin 31 | ) 32 | 33 | // A Message stores all data related to a specific Discord message. 34 | type Message struct { 35 | ID string `json:"id"` 36 | ChannelID string `json:"channel_id"` 37 | Content string `json:"content"` 38 | Timestamp Timestamp `json:"timestamp"` 39 | EditedTimestamp Timestamp `json:"edited_timestamp"` 40 | MentionRoles []string `json:"mention_roles"` 41 | Tts bool `json:"tts"` 42 | MentionEveryone bool `json:"mention_everyone"` 43 | Author *User `json:"author"` 44 | Attachments []*MessageAttachment `json:"attachments"` 45 | Embeds []*MessageEmbed `json:"embeds"` 46 | Mentions []*User `json:"mentions"` 47 | Reactions []*MessageReactions `json:"reactions"` 48 | Type MessageType `json:"type"` 49 | } 50 | 51 | // File stores info about files you e.g. send in messages. 52 | type File struct { 53 | Name string 54 | ContentType string 55 | Reader io.Reader 56 | } 57 | 58 | // MessageSend stores all parameters you can send with ChannelMessageSendComplex. 59 | type MessageSend struct { 60 | Content string `json:"content,omitempty"` 61 | Embed *MessageEmbed `json:"embed,omitempty"` 62 | Tts bool `json:"tts"` 63 | Files []*File `json:"-"` 64 | 65 | // TODO: Remove this when compatibility is not required. 66 | File *File `json:"-"` 67 | } 68 | 69 | // MessageEdit is used to chain parameters via ChannelMessageEditComplex, which 70 | // is also where you should get the instance from. 71 | type MessageEdit struct { 72 | Content *string `json:"content,omitempty"` 73 | Embed *MessageEmbed `json:"embed,omitempty"` 74 | 75 | ID string 76 | Channel string 77 | } 78 | 79 | // NewMessageEdit returns a MessageEdit struct, initialized 80 | // with the Channel and ID. 81 | func NewMessageEdit(channelID string, messageID string) *MessageEdit { 82 | return &MessageEdit{ 83 | Channel: channelID, 84 | ID: messageID, 85 | } 86 | } 87 | 88 | // SetContent is the same as setting the variable Content, 89 | // except it doesn't take a pointer. 90 | func (m *MessageEdit) SetContent(str string) *MessageEdit { 91 | m.Content = &str 92 | return m 93 | } 94 | 95 | // SetEmbed is a convenience function for setting the embed, 96 | // so you can chain commands. 97 | func (m *MessageEdit) SetEmbed(embed *MessageEmbed) *MessageEdit { 98 | m.Embed = embed 99 | return m 100 | } 101 | 102 | // A MessageAttachment stores data for message attachments. 103 | type MessageAttachment struct { 104 | ID string `json:"id"` 105 | URL string `json:"url"` 106 | ProxyURL string `json:"proxy_url"` 107 | Filename string `json:"filename"` 108 | Width int `json:"width"` 109 | Height int `json:"height"` 110 | Size int `json:"size"` 111 | } 112 | 113 | // MessageEmbedFooter is a part of a MessageEmbed struct. 114 | type MessageEmbedFooter struct { 115 | Text string `json:"text,omitempty"` 116 | IconURL string `json:"icon_url,omitempty"` 117 | ProxyIconURL string `json:"proxy_icon_url,omitempty"` 118 | } 119 | 120 | // MessageEmbedImage is a part of a MessageEmbed struct. 121 | type MessageEmbedImage struct { 122 | URL string `json:"url,omitempty"` 123 | ProxyURL string `json:"proxy_url,omitempty"` 124 | Width int `json:"width,omitempty"` 125 | Height int `json:"height,omitempty"` 126 | } 127 | 128 | // MessageEmbedThumbnail is a part of a MessageEmbed struct. 129 | type MessageEmbedThumbnail struct { 130 | URL string `json:"url,omitempty"` 131 | ProxyURL string `json:"proxy_url,omitempty"` 132 | Width int `json:"width,omitempty"` 133 | Height int `json:"height,omitempty"` 134 | } 135 | 136 | // MessageEmbedVideo is a part of a MessageEmbed struct. 137 | type MessageEmbedVideo struct { 138 | URL string `json:"url,omitempty"` 139 | ProxyURL string `json:"proxy_url,omitempty"` 140 | Width int `json:"width,omitempty"` 141 | Height int `json:"height,omitempty"` 142 | } 143 | 144 | // MessageEmbedProvider is a part of a MessageEmbed struct. 145 | type MessageEmbedProvider struct { 146 | URL string `json:"url,omitempty"` 147 | Name string `json:"name,omitempty"` 148 | } 149 | 150 | // MessageEmbedAuthor is a part of a MessageEmbed struct. 151 | type MessageEmbedAuthor struct { 152 | URL string `json:"url,omitempty"` 153 | Name string `json:"name,omitempty"` 154 | IconURL string `json:"icon_url,omitempty"` 155 | ProxyIconURL string `json:"proxy_icon_url,omitempty"` 156 | } 157 | 158 | // MessageEmbedField is a part of a MessageEmbed struct. 159 | type MessageEmbedField struct { 160 | Name string `json:"name,omitempty"` 161 | Value string `json:"value,omitempty"` 162 | Inline bool `json:"inline,omitempty"` 163 | } 164 | 165 | // An MessageEmbed stores data for message embeds. 166 | type MessageEmbed struct { 167 | URL string `json:"url,omitempty"` 168 | Type string `json:"type,omitempty"` 169 | Title string `json:"title,omitempty"` 170 | Description string `json:"description,omitempty"` 171 | Timestamp string `json:"timestamp,omitempty"` 172 | Color int `json:"color,omitempty"` 173 | Footer *MessageEmbedFooter `json:"footer,omitempty"` 174 | Image *MessageEmbedImage `json:"image,omitempty"` 175 | Thumbnail *MessageEmbedThumbnail `json:"thumbnail,omitempty"` 176 | Video *MessageEmbedVideo `json:"video,omitempty"` 177 | Provider *MessageEmbedProvider `json:"provider,omitempty"` 178 | Author *MessageEmbedAuthor `json:"author,omitempty"` 179 | Fields []*MessageEmbedField `json:"fields,omitempty"` 180 | } 181 | 182 | // MessageReactions holds a reactions object for a message. 183 | type MessageReactions struct { 184 | Count int `json:"count"` 185 | Me bool `json:"me"` 186 | Emoji *Emoji `json:"emoji"` 187 | } 188 | 189 | // ContentWithMentionsReplaced will replace all @ mentions with the 190 | // username of the mention. 191 | func (m *Message) ContentWithMentionsReplaced() (content string) { 192 | content = m.Content 193 | 194 | for _, user := range m.Mentions { 195 | content = strings.NewReplacer( 196 | "<@"+user.ID+">", "@"+user.Username, 197 | "<@!"+user.ID+">", "@"+user.Username, 198 | ).Replace(content) 199 | } 200 | return 201 | } 202 | 203 | var patternChannels = regexp.MustCompile("<#[^>]*>") 204 | 205 | // ContentWithMoreMentionsReplaced will replace all @ mentions with the 206 | // username of the mention, but also role IDs and more. 207 | func (m *Message) ContentWithMoreMentionsReplaced(s *Session) (content string, err error) { 208 | content = m.Content 209 | 210 | if !s.StateEnabled { 211 | content = m.ContentWithMentionsReplaced() 212 | return 213 | } 214 | 215 | channel, err := s.State.Channel(m.ChannelID) 216 | if err != nil { 217 | content = m.ContentWithMentionsReplaced() 218 | return 219 | } 220 | 221 | for _, user := range m.Mentions { 222 | nick := user.Username 223 | 224 | member, err := s.State.Member(channel.GuildID, user.ID) 225 | if err == nil && member.Nick != "" { 226 | nick = member.Nick 227 | } 228 | 229 | content = strings.NewReplacer( 230 | "<@"+user.ID+">", "@"+user.Username, 231 | "<@!"+user.ID+">", "@"+nick, 232 | ).Replace(content) 233 | } 234 | for _, roleID := range m.MentionRoles { 235 | role, err := s.State.Role(channel.GuildID, roleID) 236 | if err != nil || !role.Mentionable { 237 | continue 238 | } 239 | 240 | content = strings.Replace(content, "<&"+role.ID+">", "@"+role.Name, -1) 241 | } 242 | 243 | content = patternChannels.ReplaceAllStringFunc(content, func(mention string) string { 244 | channel, err := s.State.Channel(mention[2 : len(mention)-1]) 245 | if err != nil || channel.Type == ChannelTypeGuildVoice { 246 | return mention 247 | } 248 | 249 | return "#" + channel.Name 250 | }) 251 | return 252 | } 253 | -------------------------------------------------------------------------------- /vendor/github.com/bwmarrin/discordgo/endpoints.go: -------------------------------------------------------------------------------- 1 | // Discordgo - Discord bindings for Go 2 | // Available at https://github.com/bwmarrin/discordgo 3 | 4 | // Copyright 2015-2016 Bruce Marriner . All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | // This file contains variables for all known Discord end points. All functions 9 | // throughout the Discordgo package use these variables for all connections 10 | // to Discord. These are all exported and you may modify them if needed. 11 | 12 | package discordgo 13 | 14 | // APIVersion is the Discord API version used for the REST and Websocket API. 15 | var APIVersion = "6" 16 | 17 | // Known Discord API Endpoints. 18 | var ( 19 | EndpointStatus = "https://status.discordapp.com/api/v2/" 20 | EndpointSm = EndpointStatus + "scheduled-maintenances/" 21 | EndpointSmActive = EndpointSm + "active.json" 22 | EndpointSmUpcoming = EndpointSm + "upcoming.json" 23 | 24 | EndpointDiscord = "https://discordapp.com/" 25 | EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/" 26 | EndpointGuilds = EndpointAPI + "guilds/" 27 | EndpointChannels = EndpointAPI + "channels/" 28 | EndpointUsers = EndpointAPI + "users/" 29 | EndpointGateway = EndpointAPI + "gateway" 30 | EndpointGatewayBot = EndpointGateway + "/bot" 31 | EndpointWebhooks = EndpointAPI + "webhooks/" 32 | 33 | EndpointCDN = "https://cdn.discordapp.com/" 34 | EndpointCDNAttachments = EndpointCDN + "attachments/" 35 | EndpointCDNAvatars = EndpointCDN + "avatars/" 36 | EndpointCDNIcons = EndpointCDN + "icons/" 37 | EndpointCDNSplashes = EndpointCDN + "splashes/" 38 | EndpointCDNChannelIcons = EndpointCDN + "channel-icons/" 39 | 40 | EndpointAuth = EndpointAPI + "auth/" 41 | EndpointLogin = EndpointAuth + "login" 42 | EndpointLogout = EndpointAuth + "logout" 43 | EndpointVerify = EndpointAuth + "verify" 44 | EndpointVerifyResend = EndpointAuth + "verify/resend" 45 | EndpointForgotPassword = EndpointAuth + "forgot" 46 | EndpointResetPassword = EndpointAuth + "reset" 47 | EndpointRegister = EndpointAuth + "register" 48 | 49 | EndpointVoice = EndpointAPI + "/voice/" 50 | EndpointVoiceRegions = EndpointVoice + "regions" 51 | EndpointVoiceIce = EndpointVoice + "ice" 52 | 53 | EndpointTutorial = EndpointAPI + "tutorial/" 54 | EndpointTutorialIndicators = EndpointTutorial + "indicators" 55 | 56 | EndpointTrack = EndpointAPI + "track" 57 | EndpointSso = EndpointAPI + "sso" 58 | EndpointReport = EndpointAPI + "report" 59 | EndpointIntegrations = EndpointAPI + "integrations" 60 | 61 | EndpointUser = func(uID string) string { return EndpointUsers + uID } 62 | EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" } 63 | EndpointUserAvatarAnimated = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".gif" } 64 | EndpointUserSettings = func(uID string) string { return EndpointUsers + uID + "/settings" } 65 | EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" } 66 | EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID } 67 | EndpointUserGuildSettings = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID + "/settings" } 68 | EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" } 69 | EndpointUserDevices = func(uID string) string { return EndpointUsers + uID + "/devices" } 70 | EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" } 71 | EndpointUserNotes = func(uID string) string { return EndpointUsers + "@me/notes/" + uID } 72 | 73 | EndpointGuild = func(gID string) string { return EndpointGuilds + gID } 74 | EndpointGuildInivtes = func(gID string) string { return EndpointGuilds + gID + "/invites" } 75 | EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" } 76 | EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" } 77 | EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID } 78 | EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID } 79 | EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" } 80 | EndpointGuildBan = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID } 81 | EndpointGuildIntegrations = func(gID string) string { return EndpointGuilds + gID + "/integrations" } 82 | EndpointGuildIntegration = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID } 83 | EndpointGuildIntegrationSync = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID + "/sync" } 84 | EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" } 85 | EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID } 86 | EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" } 87 | EndpointGuildEmbed = func(gID string) string { return EndpointGuilds + gID + "/embed" } 88 | EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" } 89 | EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" } 90 | EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" } 91 | EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" } 92 | 93 | EndpointChannel = func(cID string) string { return EndpointChannels + cID } 94 | EndpointChannelPermissions = func(cID string) string { return EndpointChannels + cID + "/permissions" } 95 | EndpointChannelPermission = func(cID, tID string) string { return EndpointChannels + cID + "/permissions/" + tID } 96 | EndpointChannelInvites = func(cID string) string { return EndpointChannels + cID + "/invites" } 97 | EndpointChannelTyping = func(cID string) string { return EndpointChannels + cID + "/typing" } 98 | EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" } 99 | EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID } 100 | EndpointChannelMessageAck = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID + "/ack" } 101 | EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk_delete" } 102 | EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" } 103 | EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID } 104 | 105 | EndpointGroupIcon = func(cID, hash string) string { return EndpointCDNChannelIcons + cID + "/" + hash + ".png" } 106 | 107 | EndpointChannelWebhooks = func(cID string) string { return EndpointChannel(cID) + "/webhooks" } 108 | EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID } 109 | EndpointWebhookToken = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token } 110 | 111 | EndpointMessageReactionsAll = func(cID, mID string) string { 112 | return EndpointChannelMessage(cID, mID) + "/reactions" 113 | } 114 | EndpointMessageReactions = func(cID, mID, eID string) string { 115 | return EndpointChannelMessage(cID, mID) + "/reactions/" + eID 116 | } 117 | EndpointMessageReaction = func(cID, mID, eID, uID string) string { 118 | return EndpointMessageReactions(cID, mID, eID) + "/" + uID 119 | } 120 | 121 | EndpointRelationships = func() string { return EndpointUsers + "@me" + "/relationships" } 122 | EndpointRelationship = func(uID string) string { return EndpointRelationships() + "/" + uID } 123 | EndpointRelationshipsMutual = func(uID string) string { return EndpointUsers + uID + "/relationships" } 124 | 125 | EndpointInvite = func(iID string) string { return EndpointAPI + "invite/" + iID } 126 | 127 | EndpointIntegrationsJoin = func(iID string) string { return EndpointAPI + "integrations/" + iID + "/join" } 128 | 129 | EndpointEmoji = func(eID string) string { return EndpointAPI + "emojis/" + eID + ".png" } 130 | 131 | EndpointOauth2 = EndpointAPI + "oauth2/" 132 | EndpointApplications = EndpointOauth2 + "applications" 133 | EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID } 134 | EndpointApplicationsBot = func(aID string) string { return EndpointApplications + "/" + aID + "/bot" } 135 | ) 136 | --------------------------------------------------------------------------------