@ -0,0 +1,95 @@ | |||
package main | |||
import ( | |||
"fmt" | |||
"github.com/bwmarrin/discordgo" | |||
"github.com/kiliankoe/openmensa" | |||
"os" | |||
"os/signal" | |||
"strings" | |||
"syscall" | |||
"time" | |||
) | |||
var canteen *openmensa.Canteen | |||
func main() { | |||
if len(os.Args) <= 0 { | |||
println("Please add token as cli argument.") | |||
return | |||
} | |||
dg, err := discordgo.New(fmt.Sprintf("Bot %s", os.Args[1])) | |||
canteens, err := openmensa.GetCanteens(175) | |||
if err != nil { | |||
fmt.Println("error creating discord bot: ", err) | |||
return | |||
} | |||
dg.AddHandler(messageCreate) | |||
canteen = canteens[0] | |||
// Open a websocket connection to Discord and begin listening. | |||
err = dg.Open() | |||
if err != nil { | |||
fmt.Println("error opening connection,", err) | |||
return | |||
} | |||
// Wait here until CTRL-C or other term signal is received. | |||
fmt.Println("Bot is now running. Press CTRL-C to exit.") | |||
sc := make(chan os.Signal, 1) | |||
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) | |||
<-sc | |||
// Cleanly close down the Discord session. | |||
dg.Close() | |||
} | |||
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { | |||
// Ignore all messages created by the bot itself | |||
// This isn't required in this specific example but it's a good practice. | |||
if m.Author.ID == s.State.User.ID { | |||
return | |||
} | |||
if m.Content == "!mensa" { | |||
sendMealsForDate(s, time.Now(), m.ChannelID) | |||
} | |||
} | |||
func sendMealsForDate(s *discordgo.Session, t time.Time, channel string) { | |||
msg, err := s.ChannelMessageSend(channel, "Just a second...") | |||
if err != nil { | |||
println("error sending message: ", err) | |||
return | |||
} | |||
meals, err := canteen.GetMeals(t) | |||
if err != nil { | |||
s.ChannelMessageEdit(channel, msg.ID, "An error occured :angry:") | |||
return | |||
} | |||
var messages = make([]*discordgo.MessageEmbedField, len(meals)) | |||
var footer = "" | |||
for i, meal := range meals { | |||
messages[i] = &discordgo.MessageEmbedField{ | |||
Name: fmt.Sprintf("%s: %s", meal.Category, meal.Name), | |||
Value: fmt.Sprintf("%.2fโฌ", *meal.Prices.Students), | |||
} | |||
if len(meal.Notes) > 0 { | |||
footer += fmt.Sprintf("[%d] %s\n", i + 1, strings.Join(meal.Notes, ", ")) | |||
} | |||
} | |||
empty := "" | |||
s.ChannelMessageEditComplex(&discordgo.MessageEdit{ | |||
Embed: &discordgo.MessageEmbed{ | |||
Title: fmt.Sprintf("** :spaghetti: Menรผ vom %s**", t.Format("02.01.2006")), | |||
Fields: messages, | |||
Footer: &discordgo.MessageEmbedFooter{ | |||
IconURL: "https://pbs.twimg.com/profile_images/643755515118505984/xzZMK7fU_400x400.png", | |||
Text: footer, | |||
}, | |||
}, | |||
ID: msg.ID, | |||
Channel: msg.ChannelID, | |||
Content: &empty, | |||
}) | |||
} |
@ -0,0 +1,8 @@ | |||
module git.jonasfranz.software/mensabot | |||
require ( | |||
github.com/bwmarrin/discordgo v0.18.0 | |||
github.com/gorilla/websocket v1.4.0 // indirect | |||
github.com/kiliankoe/openmensa v0.0.0-20160914233745-a68c3aca59c0 | |||
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774 // indirect | |||
) |
@ -0,0 +1,14 @@ | |||
language: go | |||
go: | |||
- 1.7 | |||
- 1.8 | |||
- 1.9 | |||
install: | |||
- go get github.com/bwmarrin/discordgo | |||
- go get -v . | |||
- go get -v github.com/golang/lint/golint | |||
script: | |||
- diff <(gofmt -d .) <(echo -n) | |||
- go vet -x ./... | |||
- golint -set_exit_status ./... | |||
- go test -v -race ./... |
@ -0,0 +1,28 @@ | |||
Copyright (c) 2015, Bruce Marriner | |||
All rights reserved. | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions are met: | |||
* Redistributions of source code must retain the above copyright notice, this | |||
list of conditions and the following disclaimer. | |||
* Redistributions in binary form must reproduce the above copyright notice, | |||
this list of conditions and the following disclaimer in the documentation | |||
and/or other materials provided with the distribution. | |||
* Neither the name of discordgo nor the names of its | |||
contributors may be used to endorse or promote products derived from | |||
this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
@ -0,0 +1,130 @@ | |||
# DiscordGo | |||
[](https://godoc.org/github.com/bwmarrin/discordgo) [](http://goreportcard.com/report/bwmarrin/discordgo) [](https://travis-ci.org/bwmarrin/discordgo) [](https://discord.gg/0f1SbxBZjYoCtNPP) [](https://discord.gg/0SBTUU1wZTWT6sqd) | |||
<img align="right" src="http://bwmarrin.github.io/discordgo/img/discordgo.png"> | |||
DiscordGo is a [Go](https://golang.org/) package that provides low level | |||
bindings to the [Discord](https://discordapp.com/) chat client API. DiscordGo | |||
has nearly complete support for all of the Discord API endpoints, websocket | |||
interface, and voice interface. | |||
If you would like to help the DiscordGo package please use | |||
[this link](https://discordapp.com/oauth2/authorize?client_id=173113690092994561&scope=bot) | |||
to add the official DiscordGo test bot **dgo** to your server. This provides | |||
indispensable help to this project. | |||
* See [dgVoice](https://github.com/bwmarrin/dgvoice) package for an example of | |||
additional voice helper functions and features for DiscordGo | |||
* See [dca](https://github.com/bwmarrin/dca) for an **experimental** stand alone | |||
tool that wraps `ffmpeg` to create opus encoded audio appropriate for use with | |||
Discord (and DiscordGo) | |||
**For help with this package or general Go discussion, please join the [Discord | |||
Gophers](https://discord.gg/0f1SbxBZjYq9jLBk) chat server.** | |||
## Getting Started | |||
### master vs develop Branch | |||
* The master branch represents the latest released version of DiscordGo. This | |||
branch will always have a stable and tested version of the library. Each release | |||
is tagged and you can easily download a specific release and view release notes | |||
on the github [releases](https://github.com/bwmarrin/discordgo/releases) page. | |||
* The develop branch is where all development happens and almost always has | |||
new features over the master branch. However breaking changes are frequently | |||
added to develop and even sometimes bugs are introduced. Bugs get fixed and | |||
the breaking changes get documented before pushing to master. | |||
*So, what should you use?* | |||
If you can accept the constant changing nature of *develop* then it is the | |||
recommended branch to use. Otherwise, if you want to tail behind development | |||
slightly and have a more stable package with documented releases then use *master* | |||
### Installing | |||
This assumes you already have a working Go environment, if not please see | |||
[this page](https://golang.org/doc/install) first. | |||
`go get` *will always pull the latest released version from the master branch.* | |||
```sh | |||
go get github.com/bwmarrin/discordgo | |||
``` | |||
If you want to use the develop branch, follow these steps next. | |||
```sh | |||
cd $GOPATH/src/github.com/bwmarrin/discordgo | |||
git checkout develop | |||
``` | |||
### Usage | |||
Import the package into your project. | |||
```go | |||
import "github.com/bwmarrin/discordgo" | |||
``` | |||
Construct a new Discord client which can be used to access the variety of | |||
Discord API functions and to set callback functions for Discord events. | |||
```go | |||
discord, err := discordgo.New("authentication token") | |||
``` | |||
See Documentation and Examples below for more detailed information. | |||
## Documentation | |||
**NOTICE** : This library and the Discord API are unfinished. | |||
Because of that there may be major changes to library in the future. | |||
The DiscordGo code is fairly well documented at this point and is currently | |||
the only documentation available. Both GoDoc and GoWalker (below) present | |||
that information in a nice format. | |||
- [](https://godoc.org/github.com/bwmarrin/discordgo) | |||
- [](https://gowalker.org/github.com/bwmarrin/discordgo) | |||
- Hand crafted documentation coming eventually. | |||
## Examples | |||
Below is a list of examples and other projects using DiscordGo. Please submit | |||
an issue if you would like your project added or removed from this list | |||
- [DiscordGo Examples](https://github.com/bwmarrin/discordgo/tree/master/examples) A collection of example programs written with DiscordGo | |||
- [Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo) A curated list of high quality projects using DiscordGo | |||
## Troubleshooting | |||
For help with common problems please reference the | |||
[Troubleshooting](https://github.com/bwmarrin/discordgo/wiki/Troubleshooting) | |||
section of the project wiki. | |||
## Contributing | |||
Contributions are very welcomed, however please follow the below guidelines. | |||
- First open an issue describing the bug or enhancement so it can be | |||
discussed. | |||
- Fork the develop branch and make your changes. | |||
- Try to match current naming conventions as closely as possible. | |||
- This package is intended to be a low level direct mapping of the Discord API | |||
so please avoid adding enhancements outside of that scope without first | |||
discussing it. | |||
- Create a Pull Request with your changes against the develop branch. | |||
## List of Discord APIs | |||
See [this chart](https://abal.moe/Discord/Libraries.html) for a feature | |||
comparison and list of other Discord API libraries. | |||
## Special Thanks | |||
[Chris Rhodes](https://github.com/iopred) - For the DiscordGo logo and tons of PRs |
@ -0,0 +1,146 @@ | |||
// Discordgo - Discord bindings for Go | |||
// Available at https://github.com/bwmarrin/discordgo | |||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file. | |||
// This file contains high level helper functions and easy entry points for the | |||
// entire discordgo package. These functions are beling developed and are very | |||
// experimental at this point. They will most likley change so please use the | |||
// low level functions if that's a problem. | |||
// Package discordgo provides Discord binding for Go | |||
package discordgo | |||
import ( | |||
"errors" | |||
"fmt" | |||
"net/http" | |||
"time" | |||
) | |||
// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) | |||
const VERSION = "0.18.0" | |||
// ErrMFA will be risen by New when the user has 2FA. | |||
var ErrMFA = errors.New("account has 2FA enabled") | |||
// New creates a new Discord session and will automate some startup | |||
// tasks if given enough information to do so. Currently you can pass zero | |||
// arguments and it will return an empty Discord session. | |||
// There are 3 ways to call New: | |||
// With a single auth token - All requests will use the token blindly, | |||
// no verification of the token will be done and requests may fail. | |||
// IF THE TOKEN IS FOR A BOT, IT MUST BE PREFIXED WITH `BOT ` | |||
// eg: `"Bot <token>"` | |||
// With an email and password - Discord will sign in with the provided | |||
// credentials. | |||
// With an email, password and auth token - Discord will verify the auth | |||
// token, if it is invalid it will sign in with the provided | |||
// credentials. This is the Discord recommended way to sign in. | |||
// | |||
// NOTE: While email/pass authentication is supported by DiscordGo it is | |||
// HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token | |||
// and then use that authentication token for all future connections. | |||
// Also, doing any form of automation with a user (non Bot) account may result | |||
// in that account being permanently banned from Discord. | |||
func New(args ...interface{}) (s *Session, err error) { | |||
// Create an empty Session interface. | |||
s = &Session{ | |||
State: NewState(), | |||
Ratelimiter: NewRatelimiter(), | |||
StateEnabled: true, | |||
Compress: true, | |||
ShouldReconnectOnError: true, | |||
ShardID: 0, | |||
ShardCount: 1, | |||
MaxRestRetries: 3, | |||
Client: &http.Client{Timeout: (20 * time.Second)}, | |||
sequence: new(int64), | |||
LastHeartbeatAck: time.Now().UTC(), | |||
} | |||
// If no arguments are passed return the empty Session interface. | |||
if args == nil { | |||
return | |||
} | |||
// Variables used below when parsing func arguments | |||
var auth, pass string | |||
// Parse passed arguments | |||
for _, arg := range args { | |||
switch v := arg.(type) { | |||
case []string: | |||
if len(v) > 3 { | |||
err = fmt.Errorf("too many string parameters provided") | |||
return | |||
} | |||
// First string is either token or username | |||
if len(v) > 0 { | |||
auth = v[0] | |||
} | |||
// If second string exists, it must be a password. | |||
if len(v) > 1 { | |||
pass = v[1] | |||
} | |||
// If third string exists, it must be an auth token. | |||
if len(v) > 2 { | |||
s.Token = v[2] | |||
} | |||
case string: | |||
// First string must be either auth token or username. | |||
// Second string must be a password. | |||
// Only 2 input strings are supported. | |||
if auth == "" { | |||
auth = v | |||
} else if pass == "" { | |||
pass = v | |||
} else if s.Token == "" { | |||
s.Token = v | |||
} else { | |||
err = fmt.Errorf("too many string parameters provided") | |||
return | |||
} | |||
// case Config: | |||
// TODO: Parse configuration struct | |||
default: | |||
err = fmt.Errorf("unsupported parameter type provided") | |||
return | |||
} | |||
} | |||
// If only one string was provided, assume it is an auth token. | |||
// Otherwise get auth token from Discord, if a token was specified | |||
// Discord will verify it for free, or log the user in if it is | |||
// invalid. | |||
if pass == "" { | |||
s.Token = auth | |||
} else { | |||
err = s.Login(auth, pass) | |||
if err != nil || s.Token == "" { | |||
if s.MFA { | |||
err = ErrMFA | |||
} else { | |||
err = fmt.Errorf("Unable to fetch discord authentication token. %v", err) | |||
} | |||
return | |||
} | |||
} | |||
// The Session is now able to have RestAPI methods called on it. | |||
// It is recommended that you now call Open() so that events will trigger. | |||
return | |||
} |
@ -0,0 +1,136 @@ | |||
// Discordgo - Discord bindings for Go | |||
// Available at https://github.com/bwmarrin/discordgo | |||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file. | |||
// This file contains variables for all known Discord end points. All functions | |||
// throughout the Discordgo package use these variables for all connections | |||
// to Discord. These are all exported and you may modify them if needed. | |||
package discordgo | |||
// APIVersion is the Discord API version used for the REST and Websocket API. | |||
var APIVersion = "6" | |||
// Known Discord API Endpoints. | |||
var ( | |||
EndpointStatus = "https://status.discordapp.com/api/v2/" | |||
EndpointSm = EndpointStatus + "scheduled-maintenances/" | |||
EndpointSmActive = EndpointSm + "active.json" | |||
EndpointSmUpcoming = EndpointSm + "upcoming.json" | |||
EndpointDiscord = "https://discordapp.com/" | |||
EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/" | |||
EndpointGuilds = EndpointAPI + "guilds/" | |||
EndpointChannels = EndpointAPI + "channels/" | |||
EndpointUsers = EndpointAPI + "users/" | |||
EndpointGateway = EndpointAPI + "gateway" | |||
EndpointGatewayBot = EndpointGateway + "/bot" | |||
EndpointWebhooks = EndpointAPI + "webhooks/" | |||
EndpointCDN = "https://cdn.discordapp.com/" | |||
EndpointCDNAttachments = EndpointCDN + "attachments/" | |||
EndpointCDNAvatars = EndpointCDN + "avatars/" | |||
EndpointCDNIcons = EndpointCDN + "icons/" | |||
EndpointCDNSplashes = EndpointCDN + "splashes/" | |||
EndpointCDNChannelIcons = EndpointCDN + "channel-icons/" | |||
EndpointAuth = EndpointAPI + "auth/" | |||
EndpointLogin = EndpointAuth + "login" | |||
EndpointLogout = EndpointAuth + "logout" | |||
EndpointVerify = EndpointAuth + "verify" | |||
EndpointVerifyResend = EndpointAuth + "verify/resend" | |||
EndpointForgotPassword = EndpointAuth + "forgot" | |||
EndpointResetPassword = EndpointAuth + "reset" | |||
EndpointRegister = EndpointAuth + "register" | |||
EndpointVoice = EndpointAPI + "/voice/" | |||
EndpointVoiceRegions = EndpointVoice + "regions" | |||
EndpointVoiceIce = EndpointVoice + "ice" | |||
EndpointTutorial = EndpointAPI + "tutorial/" | |||
EndpointTutorialIndicators = EndpointTutorial + "indicators" | |||
EndpointTrack = EndpointAPI + "track" | |||
EndpointSso = EndpointAPI + "sso" | |||
EndpointReport = EndpointAPI + "report" | |||
EndpointIntegrations = EndpointAPI + "integrations" | |||
EndpointUser = func(uID string) string { return EndpointUsers + uID } | |||
EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" } | |||
EndpointUserAvatarAnimated = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".gif" } | |||
EndpointUserSettings = func(uID string) string { return EndpointUsers + uID + "/settings" } | |||
EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" } | |||
EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID } | |||
EndpointUserGuildSettings = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID + "/settings" } | |||
EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" } | |||
EndpointUserDevices = func(uID string) string { return EndpointUsers + uID + "/devices" } | |||
EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" } | |||
EndpointUserNotes = func(uID string) string { return EndpointUsers + "@me/notes/" + uID } | |||
EndpointGuild = func(gID string) string { return EndpointGuilds + gID } | |||
EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" } | |||
EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" } | |||
EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID } | |||
EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID } | |||
EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" } | |||
EndpointGuildBan = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID } | |||
EndpointGuildIntegrations = func(gID string) string { return EndpointGuilds + gID + "/integrations" } | |||
EndpointGuildIntegration = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID } | |||
EndpointGuildIntegrationSync = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID + "/sync" } | |||
EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" } | |||
EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID } | |||
EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" } | |||
EndpointGuildEmbed = func(gID string) string { return EndpointGuilds + gID + "/embed" } | |||
EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" } | |||
EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" } | |||
EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" } | |||
EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" } | |||
EndpointChannel = func(cID string) string { return EndpointChannels + cID } | |||
EndpointChannelPermissions = func(cID string) string { return EndpointChannels + cID + "/permissions" } | |||
EndpointChannelPermission = func(cID, tID string) string { return EndpointChannels + cID + "/permissions/" + tID } | |||
EndpointChannelInvites = func(cID string) string { return EndpointChannels + cID + "/invites" } | |||
EndpointChannelTyping = func(cID string) string { return EndpointChannels + cID + "/typing" } | |||
EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" } | |||
EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID } | |||
EndpointChannelMessageAck = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID + "/ack" } | |||
EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" } | |||
EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" } | |||
EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID } | |||
EndpointGroupIcon = func(cID, hash string) string { return EndpointCDNChannelIcons + cID + "/" + hash + ".png" } | |||
EndpointChannelWebhooks = func(cID string) string { return EndpointChannel(cID) + "/webhooks" } | |||
EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID } | |||
EndpointWebhookToken = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token } | |||
EndpointMessageReactionsAll = func(cID, mID string) string { | |||
return EndpointChannelMessage(cID, mID) + "/reactions" | |||
} | |||
EndpointMessageReactions = func(cID, mID, eID string) string { | |||
return EndpointChannelMessage(cID, mID) + "/reactions/" + eID | |||
} | |||
EndpointMessageReaction = func(cID, mID, eID, uID string) string { | |||
return EndpointMessageReactions(cID, mID, eID) + "/" + uID | |||
} | |||
EndpointRelationships = func() string { return EndpointUsers + "@me" + "/relationships" } | |||
EndpointRelationship = func(uID string) string { return EndpointRelationships() + "/" + uID } | |||
EndpointRelationshipsMutual = func(uID string) string { return EndpointUsers + uID + "/relationships" } | |||
EndpointGuildCreate = EndpointAPI + "guilds" | |||
EndpointInvite = func(iID string) string { return EndpointAPI + "invite/" + iID } | |||
EndpointIntegrationsJoin = func(iID string) string { return EndpointAPI + "integrations/" + iID + "/join" } | |||
EndpointEmoji = func(eID string) string { return EndpointAPI + "emojis/" + eID + ".png" } | |||
EndpointOauth2 = EndpointAPI + "oauth2/" | |||
EndpointApplications = EndpointOauth2 + "applications" | |||
EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID } | |||
EndpointApplicationsBot = func(aID string) string { return EndpointApplications + "/" + aID + "/bot" } | |||
) |
@ -0,0 +1,238 @@ | |||
package discordgo | |||
// EventHandler is an interface for Discord events. | |||
type EventHandler interface { | |||
// Type returns the type of event this handler belongs to. | |||
Type() string | |||
// Handle is called whenever an event of Type() happens. | |||
// It is the receivers responsibility to type assert that the interface | |||
// is the expected struct. | |||
Handle(*Session, interface{}) | |||
} | |||
// EventInterfaceProvider is an interface for providing empty interfaces for | |||
// Discord events. | |||
type EventInterfaceProvider interface { | |||
// Type is the type of event this handler belongs to. | |||
Type() string | |||
// New returns a new instance of the struct this event handler handles. | |||
// This is called once per event. | |||
// The struct is provided to all handlers of the same Type(). | |||
New() interface{} | |||
} | |||
// interfaceEventType is the event handler type for interface{} events. | |||
const interfaceEventType = "__INTERFACE__" | |||
// interfaceEventHandler is an event handler for interface{} events. | |||
type interfaceEventHandler func(*Session, interface{}) | |||
// Type returns the event type for interface{} events. | |||
func (eh interfaceEventHandler) Type() string { | |||
return interfaceEventType | |||
} | |||
// Handle is the handler for an interface{} event. | |||
func (eh interfaceEventHandler) Handle(s *Session, i interface{}) { | |||
eh(s, i) | |||
} | |||
var registeredInterfaceProviders = map[string]EventInterfaceProvider{} | |||
// registerInterfaceProvider registers a provider so that DiscordGo can | |||
// access it's New() method. | |||
func registerInterfaceProvider(eh EventInterfaceProvider) { | |||
if _, ok := registeredInterfaceProviders[eh.Type()]; ok { | |||
return | |||
// XXX: | |||
// if we should error here, we need to do something with it. | |||
// fmt.Errorf("event %s already registered", eh.Type()) | |||
} | |||
registeredInterfaceProviders[eh.Type()] = eh | |||
return | |||
} | |||
// eventHandlerInstance is a wrapper around an event handler, as functions | |||
// cannot be compared directly. | |||
type eventHandlerInstance struct { | |||
eventHandler EventHandler | |||
} | |||
// addEventHandler adds an event handler that will be fired anytime | |||
// the Discord WSAPI matching eventHandler.Type() fires. | |||
func (s *Session) addEventHandler(eventHandler EventHandler) func() { | |||
s.handlersMu.Lock() | |||
defer s.handlersMu.Unlock() | |||
if s.handlers == nil { | |||
s.handlers = map[string][]*eventHandlerInstance{} | |||
} | |||
ehi := &eventHandlerInstance{eventHandler} | |||
s.handlers[eventHandler.Type()] = append(s.handlers[eventHandler.Type()], ehi) | |||
return func() { | |||
s.removeEventHandlerInstance(eventHandler.Type(), ehi) | |||
} | |||
} | |||
// addEventHandler adds an event handler that will be fired the next time | |||
// the Discord WSAPI matching eventHandler.Type() fires. | |||
func (s *Session) addEventHandlerOnce(eventHandler EventHandler) func() { | |||
s.handlersMu.Lock() | |||
defer s.handlersMu.Unlock() | |||
if s.onceHandlers == nil { | |||
s.onceHandlers = map[string][]*eventHandlerInstance{} | |||
} | |||
ehi := &eventHandlerInstance{eventHandler} | |||
s.onceHandlers[eventHandler.Type()] = append(s.onceHandlers[eventHandler.Type()], ehi) | |||
return func() { | |||
s.removeEventHandlerInstance(eventHandler.Type(), ehi) | |||
} | |||
} | |||
// AddHandler allows you to add an event handler that will be fired anytime | |||
// the Discord WSAPI event that matches the function fires. | |||
// events.go contains all the Discord WSAPI events that can be fired. | |||
// eg: | |||
// Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { | |||
// }) | |||
// | |||
// or: | |||
// Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) { | |||
// }) | |||
// The return value of this method is a function, that when called will remove the | |||
// event handler. | |||
func (s *Session) AddHandler(handler interface{}) func() { | |||
eh := handlerForInterface(handler) | |||
if eh == nil { | |||
s.log(LogError, "Invalid handler type, handler will never be called") | |||
return func() {} | |||
} | |||
return s.addEventHandler(eh) | |||
} | |||
// AddHandlerOnce allows you to add an event handler that will be fired the next time | |||
// the Discord WSAPI event that matches the function fires. | |||
// See AddHandler for more details. | |||
func (s *Session) AddHandlerOnce(handler interface{}) func() { | |||
eh := handlerForInterface(handler) | |||
if eh == nil { | |||
s.log(LogError, "Invalid handler type, handler will never be called") | |||
return func() {} | |||
} | |||
return s.addEventHandlerOnce(eh) | |||
} | |||
// removeEventHandler instance removes an event handler instance. | |||
func (s *Session) removeEventHandlerInstance(t string, ehi *eventHandlerInstance) { | |||
s.handlersMu.Lock() | |||
defer s.handlersMu.Unlock() | |||
handlers := s.handlers[t] | |||
for i := range handlers { | |||
if handlers[i] == ehi { | |||
s.handlers[t] = append(handlers[:i], handlers[i+1:]...) | |||
} | |||
} | |||
onceHandlers := s.onceHandlers[t] | |||
for i := range onceHandlers { | |||
if onceHandlers[i] == ehi { | |||
s.onceHandlers[t] = append(onceHandlers[:i], handlers[i+1:]...) | |||
} | |||
} | |||
} | |||
// Handles calling permanent and once handlers for an event type. | |||
func (s *Session) handle(t string, i interface{}) { | |||
for _, eh := range s.handlers[t] { | |||
if s.SyncEvents { | |||
eh.eventHandler.Handle(s, i) | |||
} else { | |||
go eh.eventHandler.Handle(s, i) | |||
} | |||
} | |||
if len(s.onceHandlers[t]) > 0 { | |||
for _, eh := range s.onceHandlers[t] { | |||
if s.SyncEvents { | |||
eh.eventHandler.Handle(s, i) | |||
} else { | |||
go eh.eventHandler.Handle(s, i) | |||
} | |||
} | |||
s.onceHandlers[t] = nil | |||
} | |||
} | |||
// Handles an event type by calling internal methods, firing handlers and firing the | |||
// interface{} event. | |||
func (s *Session) handleEvent(t string, i interface{}) { | |||
s.handlersMu.RLock() | |||
defer s.handlersMu.RUnlock() | |||
// All events are dispatched internally first. | |||
s.onInterface(i) | |||
// Then they are dispatched to anyone handling interface{} events. | |||
s.handle(interfaceEventType, i) | |||
// Finally they are dispatched to any typed handlers. | |||
s.handle(t, i) | |||
} | |||
// setGuildIds will set the GuildID on all the members of a guild. | |||
// This is done as event data does not have it set. | |||
func setGuildIds(g *Guild) { | |||
for _, c := range g.Channels { | |||
c.GuildID = g.ID | |||
} | |||
for _, m := range g.Members { | |||
m.GuildID = g.ID | |||
} | |||
for _, vs := range g.VoiceStates { | |||
vs.GuildID = g.ID | |||
} | |||
} | |||
// onInterface handles all internal events and routes them to the appropriate internal handler. | |||
func (s *Session) onInterface(i interface{}) { | |||
switch t := i.(type) { | |||
case *Ready: | |||
for _, g := range t.Guilds { | |||
setGuildIds(g) | |||
} | |||
s.onReady(t) | |||
case *GuildCreate: | |||
setGuildIds(t.Guild) | |||
case *GuildUpdate: | |||
setGuildIds(t.Guild) | |||
case *VoiceServerUpdate: | |||
go s.onVoiceServerUpdate(t) | |||
case *VoiceStateUpdate: | |||
go s.onVoiceStateUpdate(t) | |||
} | |||
err := s.State.OnInterface(s, i) | |||
if err != nil { | |||
s.log(LogDebug, "error dispatching internal event, %s", err) | |||
} | |||
} | |||
// onReady handles the ready event. | |||
func (s *Session) onReady(r *Ready) { | |||
// Store the SessionID within the Session struct. | |||
s.sessionID = r.SessionID | |||
} |
@ -0,0 +1,253 @@ | |||
package discordgo | |||
import ( | |||
"encoding/json" | |||
) | |||
// This file contains all the possible structs that can be | |||
// handled by AddHandler/EventHandler. | |||
// DO NOT ADD ANYTHING BUT EVENT HANDLER STRUCTS TO THIS FILE. | |||
//go:generate go run tools/cmd/eventhandlers/main.go | |||
// Connect is the data for a Connect event. | |||
// This is a sythetic event and is not dispatched by Discord. | |||
type Connect struct{} | |||
// Disconnect is the data for a Disconnect event. | |||
// This is a sythetic event and is not dispatched by Discord. | |||
type Disconnect struct{} | |||
// RateLimit is the data for a RateLimit event. | |||
// This is a sythetic event and is not dispatched by Discord. | |||
type RateLimit struct { | |||
*TooManyRequests | |||
URL string | |||
} | |||
// Event provides a basic initial struct for all websocket events. | |||
type Event struct { | |||
Operation int `json:"op"` | |||
Sequence int64 `json:"s"` | |||
Type string `json:"t"` | |||
RawData json.RawMessage `json:"d"` | |||
// Struct contains one of the other types in this file. | |||
Struct interface{} `json:"-"` | |||
} | |||
// A Ready stores all data for the websocket READY event. | |||
type Ready struct { | |||
Version int `json:"v"` | |||
SessionID string `json:"session_id"` | |||
User *User `json:"user"` | |||
ReadState []*ReadState `json:"read_state"` | |||
PrivateChannels []*Channel `json:"private_channels"` | |||
Guilds []*Guild `json:"guilds"` | |||
// Undocumented fields | |||
Settings *Settings `json:"user_settings"` | |||
UserGuildSettings []*UserGuildSettings `json:"user_guild_settings"` | |||
Relationships []*Relationship `json:"relationships"` | |||
Presences []*Presence `json:"presences"` | |||
Notes map[string]string `json:"notes"` | |||
} | |||
// ChannelCreate is the data for a ChannelCreate event. | |||
type ChannelCreate struct { | |||
*Channel | |||
} | |||
// ChannelUpdate is the data for a ChannelUpdate event. | |||
type ChannelUpdate struct { | |||
*Channel | |||
} | |||
// ChannelDelete is the data for a ChannelDelete event. | |||
type ChannelDelete struct { | |||
*Channel | |||
} | |||
// ChannelPinsUpdate stores data for a ChannelPinsUpdate event. | |||
type ChannelPinsUpdate struct { | |||
LastPinTimestamp string `json:"last_pin_timestamp"` | |||
ChannelID string `json:"channel_id"` | |||
} | |||
// GuildCreate is the data for a GuildCreate event. | |||
type GuildCreate struct { | |||
*Guild | |||
} | |||
// GuildUpdate is the data for a GuildUpdate event. | |||
type GuildUpdate struct { | |||
*Guild | |||
} | |||
// GuildDelete is the data for a GuildDelete event. | |||
type GuildDelete struct { | |||
*Guild | |||
} | |||
// GuildBanAdd is the data for a GuildBanAdd event. | |||
type GuildBanAdd struct { | |||
User *User `json:"user"` | |||
GuildID string `json:"guild_id"` | |||
} | |||
// GuildBanRemove is the data for a GuildBanRemove event. | |||
type GuildBanRemove struct { | |||
User *User `json:"user"` | |||
GuildID string `json:"guild_id"` | |||
} | |||
// GuildMemberAdd is the data for a GuildMemberAdd event. | |||
type GuildMemberAdd struct { | |||
*Member | |||
} | |||
// GuildMemberUpdate is the data for a GuildMemberUpdate event. | |||
type GuildMemberUpdate struct { | |||
*Member | |||
} | |||
// GuildMemberRemove is the data for a GuildMemberRemove event. | |||
type GuildMemberRemove struct { | |||
*Member | |||
} | |||
// GuildRoleCreate is the data for a GuildRoleCreate event. | |||
type GuildRoleCreate struct { | |||
*GuildRole | |||
} | |||
// GuildRoleUpdate is the data for a GuildRoleUpdate event. | |||
type GuildRoleUpdate struct { | |||
*GuildRole | |||
} | |||
// A GuildRoleDelete is the data for a GuildRoleDelete event. | |||
type GuildRoleDelete struct { | |||
RoleID string `json:"role_id"` | |||
GuildID string `json:"guild_id"` | |||
} | |||
// A GuildEmojisUpdate is the data for a guild emoji update event. | |||
type GuildEmojisUpdate struct { | |||
GuildID string `json:"guild_id"` | |||
Emojis []*Emoji `json:"emojis"` | |||
} | |||
// A GuildMembersChunk is the data for a GuildMembersChunk event. | |||
type GuildMembersChunk struct { | |||
GuildID string `json:"guild_id"` | |||
Members []*Member `json:"members"` | |||
} | |||
// GuildIntegrationsUpdate is the data for a GuildIntegrationsUpdate event. | |||
type GuildIntegrationsUpdate struct { | |||
GuildID string `json:"guild_id"` | |||
} | |||
// MessageAck is the data for a MessageAck event. | |||
type MessageAck struct { | |||
MessageID string `json:"message_id"` | |||
ChannelID string `json:"channel_id"` | |||
} | |||
// MessageCreate is the data for a MessageCreate event. | |||
type MessageCreate struct { | |||
*Message | |||
} | |||
// MessageUpdate is the data for a MessageUpdate event. | |||
type MessageUpdate struct { | |||
*Message | |||
} | |||
// MessageDelete is the data for a MessageDelete event. | |||
type MessageDelete struct { | |||
*Message | |||
} | |||
// MessageReactionAdd is the data for a MessageReactionAdd event. | |||
type MessageReactionAdd struct { | |||
*MessageReaction | |||
} | |||
// MessageReactionRemove is the data for a MessageReactionRemove event. | |||
type MessageReactionRemove struct { | |||
*MessageReaction | |||
} | |||
// MessageReactionRemoveAll is the data for a MessageReactionRemoveAll event. | |||
type MessageReactionRemoveAll struct { | |||
*MessageReaction | |||
} | |||
// PresencesReplace is the data for a PresencesReplace event. | |||
type PresencesReplace []*Presence | |||
// PresenceUpdate is the data for a PresenceUpdate event. | |||
type PresenceUpdate struct { | |||
Presence | |||
GuildID string `json:"guild_id"` | |||
Roles []string `json:"roles"` | |||
} | |||
// Resumed is the data for a Resumed event. | |||
type Resumed struct { | |||
Trace []string `json:"_trace"` | |||
} | |||
// RelationshipAdd is the data for a RelationshipAdd event. | |||
type RelationshipAdd struct { | |||
*Relationship | |||
} | |||
// RelationshipRemove is the data for a RelationshipRemove event. | |||
type RelationshipRemove struct { | |||
*Relationship | |||
} | |||
// TypingStart is the data for a TypingStart event. | |||
type TypingStart struct { | |||
UserID string `json:"user_id"` | |||
ChannelID string `json:"channel_id"` | |||
Timestamp int `json:"timestamp"` | |||
} | |||
// UserUpdate is the data for a UserUpdate event. | |||
type UserUpdate struct { | |||
*User | |||
} | |||
// UserSettingsUpdate is the data for a UserSettingsUpdate event. | |||
type UserSettingsUpdate map[string]interface{} | |||
// UserGuildSettingsUpdate is the data for a UserGuildSettingsUpdate event. | |||
type UserGuildSettingsUpdate struct { | |||
*UserGuildSettings | |||
} | |||
// UserNoteUpdate is the data for a UserNoteUpdate event. | |||
type UserNoteUpdate struct { | |||
ID string `json:"id"` | |||
Note string `json:"note"` | |||
} | |||
// VoiceServerUpdate is the data for a VoiceServerUpdate event. | |||
type VoiceServerUpdate struct { | |||
Token string `json:"token"` | |||
GuildID string `json:"guild_id"` | |||
Endpoint string `json:"endpoint"` | |||
} | |||
// VoiceStateUpdate is the data for a VoiceStateUpdate event. | |||
type VoiceStateUpdate struct { | |||
*VoiceState | |||
} | |||
// MessageDeleteBulk is the data for a MessageDeleteBulk event | |||
type MessageDeleteBulk struct { | |||
Messages []string `json:"ids"` | |||
ChannelID string `json:"channel_id"` | |||
} |
@ -0,0 +1,103 @@ | |||
// Discordgo - Discord bindings for Go | |||
// Available at https://github.com/bwmarrin/discordgo | |||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file. | |||
// This file contains code related to discordgo package logging | |||
package discordgo | |||
import ( | |||
"fmt" | |||
"log" | |||
"runtime" | |||
"strings" | |||
) | |||
const ( | |||
// LogError level is used for critical errors that could lead to data loss | |||
// or panic that would not be returned to a calling function. | |||
LogError int = iota | |||
// LogWarning level is used for very abnormal events and errors that are | |||
// also returned to a calling function. | |||
LogWarning | |||
// LogInformational level is used for normal non-error activity | |||
LogInformational | |||
// LogDebug level is for very detailed non-error activity. This is | |||
// very spammy and will impact performance. | |||
LogDebug | |||
) | |||
// Logger can be used to replace the standard logging for discordgo | |||
var Logger func(msgL, caller int, format string, a ...interface{}) | |||
// msglog provides package wide logging consistancy for discordgo | |||
// the format, a... portion this command follows that of fmt.Printf | |||
// msgL : LogLevel of the message | |||
// caller : 1 + the number of callers away from the message source | |||
// format : Printf style message format | |||
// a ... : comma separated list of values to pass | |||
func msglog(msgL, caller int, format string, a ...interface{}) { | |||
if Logger != nil { | |||
Logger(msgL, caller, format, a...) | |||
} else { | |||
pc, file, line, _ := runtime.Caller(caller) | |||
files := strings.Split(file, "/") | |||
file = files[len(files)-1] | |||
name := runtime.FuncForPC(pc).Name() | |||
fns := strings.Split(name, ".") | |||
name = fns[len(fns)-1] | |||
msg := fmt.Sprintf(format, a...) | |||
log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg) | |||
} | |||
} | |||
// helper function that wraps msglog for the Session struct | |||
// This adds a check to insure the message is only logged | |||
// if the session log level is equal or higher than the | |||
// message log level | |||
func (s *Session) log(msgL int, format string, a ...interface{}) { | |||
if msgL > s.LogLevel { | |||
return | |||
} | |||
msglog(msgL, 2, format, a...) | |||
} | |||
// helper function that wraps msglog for the VoiceConnection struct | |||
// This adds a check to insure the message is only logged | |||
// if the voice connection log level is equal or higher than the | |||
// message log level | |||
func (v *VoiceConnection) log(msgL int, format string, a ...interface{}) { | |||
if msgL > v.LogLevel { | |||
return | |||
} | |||
msglog(msgL, 2, format, a...) | |||
} | |||
// printJSON is a helper function to display JSON data in a easy to read format. | |||
/* NOT USED ATM | |||
func printJSON(body []byte) { | |||
var prettyJSON bytes.Buffer | |||
error := json.Indent(&prettyJSON, body, "", "\t") | |||
if error != nil { | |||
log.Print("JSON parse error: ", error) | |||
} | |||
log.Println(string(prettyJSON.Bytes())) | |||
} | |||
*/ |
@ -0,0 +1,252 @@ | |||
// Discordgo - Discord bindings for Go | |||
// Available at https://github.com/bwmarrin/discordgo | |||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file. | |||
// This file contains code related to the Message struct | |||
package discordgo | |||
import ( | |||
"io" | |||
"regexp" | |||
"strings" | |||
) | |||
// MessageType is the type of Message | |||
type MessageType int | |||
// Block contains the valid known MessageType values | |||
const ( | |||
MessageTypeDefault MessageType = iota | |||
MessageTypeRecipientAdd | |||
MessageTypeRecipientRemove | |||
MessageTypeCall | |||
MessageTypeChannelNameChange | |||
MessageTypeChannelIconChange | |||
MessageTypeChannelPinnedMessage | |||
MessageTypeGuildMemberJoin | |||
) | |||
// A Message stores all data related to a specific Discord message. | |||
type Message struct { | |||
ID string `json:"id"` | |||
ChannelID string `json:"channel_id"` | |||
Content string `json:"content"` | |||
Timestamp Timestamp `json:"timestamp"` | |||
EditedTimestamp Timestamp `json:"edited_timestamp"` | |||
MentionRoles []string `json:"mention_roles"` | |||
Tts bool `json:"tts"` | |||
MentionEveryone bool `json:"mention_everyone"` | |||
Author *User `json:"author"` | |||
Attachments []*MessageAttachment `json:"attachments"` | |||
Embeds []*MessageEmbed `json:"embeds"` | |||
Mentions []*User `json:"mentions"` | |||
Reactions []*MessageReactions `json:"reactions"` | |||
Type MessageType `json:"type"` | |||
} | |||
// File stores info about files you e.g. send in messages. | |||
type File struct { | |||
Name string | |||
ContentType string | |||
Reader io.Reader | |||
} | |||
// MessageSend stores all parameters you can send with ChannelMessageSendComplex. | |||
type MessageSend struct { | |||
Content string `json:"content,omitempty"` | |||
Embed *MessageEmbed `json:"embed,omitempty"` | |||
Tts bool `json:"tts"` | |||
Files []*File `json:"-"` | |||
// TODO: Remove this when compatibility is not required. | |||
File *File `json:"-"` | |||
} | |||
// MessageEdit is used to chain parameters via ChannelMessageEditComplex, which | |||
// is also where you should get the instance from. | |||
type MessageEdit struct { | |||
Content *string `json:"content,omitempty"` | |||
Embed *MessageEmbed `json:"embed,omitempty"` | |||
ID string | |||
Channel string | |||
} | |||
// NewMessageEdit returns a MessageEdit struct, initialized | |||
// with the Channel and ID. | |||
func NewMessageEdit(channelID string, messageID string) *MessageEdit { | |||
return &MessageEdit{ | |||
Channel: channelID, | |||
ID: messageID, | |||
} | |||
} | |||
// SetContent is the same as setting the variable Content, | |||
// except it doesn't take a pointer. | |||
func (m *MessageEdit) SetContent(str string) *MessageEdit { | |||
m.Content = &str | |||
return m | |||
} | |||
// SetEmbed is a convenience function for setting the embed, | |||
// so you can chain commands. | |||
func (m *MessageEdit) SetEmbed(embed *MessageEmbed) *MessageEdit { | |||
m.Embed = embed | |||
return m | |||
} | |||
// A MessageAttachment stores data for message attachments. | |||
type MessageAttachment struct { | |||
ID string `json:"id"` | |||
URL string `json:"url"` | |||
ProxyURL string `json:"proxy_url"` | |||
Filename string `json:"filename"` | |||
Width int `json:"width"` | |||
Height int `json:"height"` | |||
Size int `json:"size"` | |||
} | |||
// MessageEmbedFooter is a part of a MessageEmbed struct. | |||
type MessageEmbedFooter struct { | |||
Text string `json:"text,omitempty"` | |||
IconURL string `json:"icon_url,omitempty"` | |||
ProxyIconURL string `json:"proxy_icon_url,omitempty"` | |||
} | |||
// MessageEmbedImage is a part of a MessageEmbed struct. | |||
type MessageEmbedImage struct { | |||
URL string `json:"url,omitempty"` | |||
ProxyURL string `json:"proxy_url,omitempty"` | |||
Width int `json:"width,omitempty"` | |||
Height int `json:"height,omitempty"` | |||
} | |||
// MessageEmbedThumbnail is a part of a MessageEmbed struct. | |||
type MessageEmbedThumbnail struct { | |||
URL string `json:"url,omitempty"` | |||
ProxyURL string `json:"proxy_url,omitempty"` | |||
Width int `json:"width,omitempty"` | |||
Height int `json:"height,omitempty"` | |||
} | |||
// MessageEmbedVideo is a part of a MessageEmbed struct. | |||
type MessageEmbedVideo struct { | |||
URL string `json:"url,omitempty"` | |||
ProxyURL string `json:"proxy_url,omitempty"` | |||
Width int `json:"width,omitempty"` | |||
Height int `json:"height,omitempty"` | |||
} | |||
// MessageEmbedProvider is a part of a MessageEmbed struct. | |||
type MessageEmbedProvider struct { | |||
URL string `json:"url,omitempty"` | |||
Name string `json:"name,omitempty"` | |||
} | |||
// MessageEmbedAuthor is a part of a MessageEmbed struct. | |||
type MessageEmbedAuthor struct { | |||
URL string `json:"url,omitempty"` | |||
Name string `json:"name,omitempty"` | |||
IconURL string `json:"icon_url,omitempty"` | |||
ProxyIconURL string `json:"proxy_icon_url,omitempty"` | |||
} | |||
// MessageEmbedField is a part of a MessageEmbed struct. | |||
type MessageEmbedField struct { | |||
Name string `json:"name,omitempty"` | |||
Value string `json:"value,omitempty"` | |||
Inline bool `json:"inline,omitempty"` | |||
} | |||
// An MessageEmbed stores data for message embeds. | |||
type MessageEmbed struct { | |||
URL string `json:"url,omitempty"` | |||
Type string `json:"type,omitempty"` | |||
Title string `json:"title,omitempty"` | |||
Description string `json:"description,omitempty"` | |||
Timestamp string `json:"timestamp,omitempty"` | |||
Color int `json:"color,omitempty"` | |||
Footer *MessageEmbedFooter `json:"footer,omitempty"` | |||
Image *MessageEmbedImage `json:"image,omitempty"` | |||
Thumbnail *MessageEmbedThumbnail `json:"thumbnail,omitempty"` | |||
Video *MessageEmbedVideo `json:"video,omitempty"` | |||
Provider *MessageEmbedProvider `json:"provider,omitempty"` | |||
Author *MessageEmbedAuthor `json:"author,omitempty"` | |||
Fields []*MessageEmbedField `json:"fields,omitempty"` | |||
} | |||
// MessageReactions holds a reactions object for a message. | |||
type MessageReactions struct { | |||
Count int `json:"count"` | |||
Me bool `json:"me"` | |||
Emoji *Emoji `json:"emoji"` | |||
} | |||
// ContentWithMentionsReplaced will replace all @<id> mentions with the | |||
// username of the mention. | |||
func (m *Message) ContentWithMentionsReplaced() (content string) { | |||
content = m.Content | |||
for _, user := range m.Mentions { | |||
content = strings.NewReplacer( | |||
"<@"+user.ID+">", "@"+user.Username, | |||
"<@!"+user.ID+">", "@"+user.Username, | |||
).Replace(content) | |||
} | |||
return | |||
} | |||
var patternChannels = regexp.MustCompile("<#[^>]*>") | |||
// ContentWithMoreMentionsReplaced will replace all @<id> mentions with the | |||
// username of the mention, but also role IDs and more. | |||
func (m *Message) ContentWithMoreMentionsReplaced(s *Session) (content string, err error) { | |||
content = m.Content | |||
if !s.StateEnabled { | |||
content = m.ContentWithMentionsReplaced() | |||
return | |||
} | |||
channel, err := s.State.Channel(m.ChannelID) | |||
if err != nil { | |||
content = m.ContentWithMentionsReplaced() | |||
return | |||
} | |||
for _, user := range m.Mentions { | |||
nick := user.Username | |||
member, err := s.State.Member(channel.GuildID, user.ID) | |||
if err == nil && member.Nick != "" { | |||
nick = member.Nick | |||
} | |||
content = strings.NewReplacer( | |||
"<@"+user.ID+">", "@"+user.Username, | |||
"<@!"+user.ID+">", "@"+nick, | |||
).Replace(content) | |||
} | |||
for _, roleID := range m.MentionRoles { | |||
role, err := s.State.Role(channel.GuildID, roleID) | |||
if err != nil || !role.Mentionable { | |||
continue | |||
} | |||
content = strings.Replace(content, "<&"+role.ID+">", "@"+role.Name, -1) | |||
} | |||
content = patternChannels.ReplaceAllStringFunc(content, func(mention string) string { | |||
channel, err := s.State.Channel(mention[2 : len(mention)-1]) | |||
if err != nil || channel.Type == ChannelTypeGuildVoice { | |||
return mention | |||
} | |||
return "#" + channel.Name | |||
}) | |||
return | |||
} |
@ -0,0 +1,17 @@ | |||
site_name: DiscordGo | |||
site_author: Bruce Marriner | |||
site_url: http://bwmarrin.github.io/discordgo/ | |||
repo_url: https://github.com/bwmarrin/discordgo | |||
dev_addr: 0.0.0.0:8000 | |||
theme: yeti | |||
markdown_extensions: | |||
- smarty | |||
- toc: | |||
permalink: True | |||
- sane_lists | |||
pages: | |||
- 'Home': 'index.md' | |||
- 'Getting Started': 'GettingStarted.md' |
@ -0,0 +1,126 @@ | |||
// Discordgo - Discord bindings for Go | |||
// Available at https://github.com/bwmarrin/discordgo | |||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file. | |||
// This file contains functions related to Discord OAuth2 endpoints | |||
package discordgo | |||
// ------------------------------------------------------------------------------------------------ | |||
// Code specific to Discord OAuth2 Applications | |||
// ------------------------------------------------------------------------------------------------ | |||
// An Application struct stores values for a Discord OAuth2 Application | |||
type Application struct { | |||
ID string `json:"id,omitempty"` | |||
Name string `json:"name"` | |||
Description string `json:"description,omitempty"` | |||
Icon string `json:"icon,omitempty"` | |||
Secret string `json:"secret,omitempty"` | |||
RedirectURIs *[]string `json:"redirect_uris,omitempty"` | |||
BotRequireCodeGrant bool `json:"bot_require_code_grant,omitempty"` | |||
BotPublic bool `json:"bot_public,omitempty"` | |||
RPCApplicationState int `json:"rpc_application_state,omitempty"` | |||
Flags int `json:"flags,omitempty"` | |||
Owner *User `json:"owner"` | |||
Bot *User `json:"bot"` | |||
} | |||
// Application returns an Application structure of a specific Application | |||
// appID : The ID of an Application | |||
func (s *Session) Application(appID string) (st *Application, err error) { | |||
body, err := s.RequestWithBucketID("GET", EndpointApplication(appID), nil, EndpointApplication("")) | |||
if err != nil { | |||
return | |||
} | |||
err = unmarshal(body, &st) | |||
return | |||
} | |||
// Applications returns all applications for the authenticated user | |||
func (s *Session) Applications() (st []*Application, err error) { | |||
body, err := s.RequestWithBucketID("GET", EndpointApplications, nil, EndpointApplications) | |||
if err != nil { | |||
return | |||
} | |||
err = unmarshal(body, &st) | |||
return | |||
} | |||
// ApplicationCreate creates a new Application | |||
// name : Name of Application / Bot | |||
// uris : Redirect URIs (Not required) | |||
func (s *Session) ApplicationCreate(ap *Application) (st *Application, err error) { | |||
data := struct { | |||
Name string `json:"name"` | |||
Description string `json:"description"` | |||
RedirectURIs *[]string `json:"redirect_uris,omitempty"` | |||
}{ap.Name, ap.Description, ap.RedirectURIs} | |||
body, err := s.RequestWithBucketID("POST", EndpointApplications, data, EndpointApplications) | |||
if err != nil { | |||
return | |||
} | |||
err = unmarshal(body, &st) | |||
return | |||
} | |||
// ApplicationUpdate updates an existing Application | |||
// var : desc | |||
func (s *Session) ApplicationUpdate(appID string, ap *Application) (st *Application, err error) { | |||
data := struct { | |||
Name string `json:"name"` | |||
Description string `json:"description"` | |||
RedirectURIs *[]string `json:"redirect_uris,omitempty"` | |||
}{ap.Name, ap.Description, ap.RedirectURIs} | |||
body, err := s.RequestWithBucketID("PUT", EndpointApplication(appID), data, EndpointApplication("")) | |||
if err != nil { | |||
return | |||
} | |||
err = unmarshal(body, &st) | |||
return | |||
} | |||
// ApplicationDelete deletes an existing Application | |||
// appID : The ID of an Application | |||
func (s *Session) ApplicationDelete(appID string) (err error) { | |||
_, err = s.RequestWithBucketID("DELETE", EndpointApplication(appID), nil, EndpointApplication("")) | |||
if err != nil { | |||
return | |||
} | |||
return | |||
} | |||
// ------------------------------------------------------------------------------------------------ | |||
// Code specific to Discord OAuth2 Application Bots | |||
// ------------------------------------------------------------------------------------------------ | |||
// ApplicationBotCreate creates an Application Bot Account | |||
// | |||
// appID : The ID of an Application | |||
// | |||
// NOTE: func name may change, if I can think up something better. | |||
func (s *Session) ApplicationBotCreate(appID string) (st *User, err error) { | |||
body, err := s.RequestWithBucketID("POST", EndpointApplicationsBot(appID), nil, EndpointApplicationsBot("")) | |||
if err != nil { | |||
return | |||
} | |||
err = unmarshal(body, &st) | |||
return | |||
} |
@ -0,0 +1,194 @@ | |||
package discordgo | |||
import ( | |||
"net/http" | |||
"strconv" | |||
"strings" | |||
"sync" | |||
"sync/atomic" | |||
"time" | |||
) | |||
// customRateLimit holds information for defining a custom rate limit | |||
type customRateLimit struct { | |||
suffix string | |||
requests int | |||
reset time.Duration | |||
} | |||
// RateLimiter holds all ratelimit buckets | |||
type RateLimiter struct { | |||
sync.Mutex | |||
global *int64 | |||
buckets map[string]*Bucket | |||
globalRateLimit time.Duration | |||
customRateLimits []*customRateLimit | |||
} | |||
// NewRatelimiter returns a new RateLimiter | |||
func NewRatelimiter() *RateLimiter { | |||
return &RateLimiter{ | |||
buckets: make(map[string]*Bucket), | |||
global: new(int64), | |||
customRateLimits: []*customRateLimit{ | |||
&customRateLimit{ | |||
suffix: "//reactions//", | |||
requests: 1, | |||
reset: 200 * time.Millisecond, | |||
}, | |||
}, | |||
} | |||
} | |||
// GetBucket retrieves or creates a bucket | |||
func (r *RateLimiter) GetBucket(key string) *Bucket { | |||
r.Lock() | |||
defer r.Unlock() | |||
if bucket, ok := r.buckets[key]; ok { | |||
return bucket | |||
} | |||
b := &Bucket{ | |||
Remaining: 1, | |||
Key: key, | |||
global: r.global, | |||
} | |||
// Check if there is a custom ratelimit set for this bucket ID. | |||
for _, rl := range r.customRateLimits { | |||
if strings.HasSuffix(b.Key, rl.suffix) { | |||
b.customRateLimit = rl | |||
break | |||
} | |||
} | |||
r.buckets[key] = b | |||
return b | |||
} | |||
// GetWaitTime returns the duration you should wait for a Bucket | |||
func (r *RateLimiter) GetWaitTime(b *Bucket, minRemaining int) time.Duration { | |||
// If we ran out of calls and the reset time is still ahead of us | |||
// then we need to take it easy and relax a little | |||
if b.Remaining < minRemaining && b.reset.After(time.Now()) { | |||
return b.reset.Sub(time.Now()) | |||
} | |||
// Check for global ratelimits | |||
sleepTo := time.Unix(0, atomic.LoadInt64(r.global)) | |||
if now := time.Now(); now.Before(sleepTo) { | |||
return sleepTo.Sub(now) | |||
} | |||
return 0 | |||
} | |||
// LockBucket Locks until a request can be made | |||
func (r *RateLimiter) LockBucket(bucketID string) *Bucket { | |||
return r.LockBucketObject(r.GetBucket(bucketID)) | |||
} | |||
// LockBucketObject Locks an already resolved bucket until a request can be made | |||
func (r *RateLimiter) LockBucketObject(b *Bucket) *Bucket { | |||
b.Lock() | |||
if wait := r.GetWaitTime(b, 1); wait > 0 { | |||
time.Sleep(wait) | |||
} | |||
b.Remaining-- | |||
return b | |||
} | |||
// Bucket represents a ratelimit bucket, each bucket gets ratelimited individually (-global ratelimits) | |||
type Bucket struct { | |||
sync.Mutex | |||
Key string | |||
Remaining int | |||
limit int | |||
reset time.Time | |||
global *int64 | |||
lastReset time.Time | |||
customRateLimit *customRateLimit | |||
Userdata interface{} | |||
} | |||