1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2025-12-04 19:37:39 +00:00

More things, what a pain in the ass

This commit is contained in:
diamondburned (Forefront) 2020-01-05 19:48:39 -08:00
parent d56e3fae0f
commit a386f6a359
15 changed files with 416 additions and 59 deletions

View file

@ -11,7 +11,6 @@ const (
APIVersion = "6"
Endpoint = BaseEndpoint + "/v" + APIVersion + "/"
EndpointUsers = Endpoint + "users/"
EndpointGateway = Endpoint + "gateway"
EndpointGatewayBot = EndpointGateway + "/bot"
EndpointWebhooks = Endpoint + "webhooks/"

View file

@ -1,6 +1,8 @@
package api
import (
"io"
"github.com/diamondburned/arikawa/discord" // for clarity
d "github.com/diamondburned/arikawa/discord"
"github.com/diamondburned/arikawa/httputil"
@ -10,9 +12,8 @@ const EndpointGuilds = Endpoint + "guilds/"
// https://discordapp.com/developers/docs/resources/guild#create-guild-json-params
type CreateGuildData struct {
Name string `json:"name"`
Region string `json:"region"`
Icon Image `json:"image"`
Name string `json:"name"`
Icon Image `json:"image,omitempty"`
// package dc is just package discord
Verification d.Verification `json:"verification_level"`
@ -20,10 +21,13 @@ type CreateGuildData struct {
ExplicitFilter d.ExplicitFilter `json:"explicit_content_filter"`
// [0] (First entry) is ALWAYS @everyone.
Roles []discord.Role `json:"roles"`
Roles []discord.Role `json:"roles,omitempty"`
// Voice only
VoiceRegion string `json:"region,omitempty"`
// Partial, id field is ignored. Usually only Name and Type are changed.
Channels []discord.Channel `json:"channels"`
Channels []discord.Channel `json:"channels,omitempty"`
}
func (c *Client) CreateGuild(data CreateGuildData) (*discord.Guild, error) {
@ -41,7 +45,7 @@ func (c *Client) Guild(guildID discord.Snowflake) (*discord.Guild, error) {
type ModifyGuildData struct {
Name string `json:"name,omitempty"`
Region string `json:"region,omitempty"`
Icon *Image `json:"image,omitempty"`
Icon Image `json:"image,omitempty"`
// package d is just package discord
Verification *d.Verification `json:"verification_level,omitempty"`
@ -53,8 +57,8 @@ type ModifyGuildData struct {
OwnerID d.Snowflake `json:"owner_id,string,omitempty"`
Splash *Image `json:"splash,omitempty"`
Banner *Image `json:"banner,omitempty"`
Splash Image `json:"splash,omitempty"`
Banner Image `json:"banner,omitempty"`
SystemChannelID d.Snowflake `json:"system_channel_id,string,omitempty"`
}
@ -71,10 +75,24 @@ func (c *Client) DeleteGuild(guildID discord.Snowflake) error {
return c.FastRequest("DELETE", EndpointGuilds+guildID.String())
}
func (c *Client) Members(guildID discord.Snowflake) ([]discord.Member, error) {
// Members returns maximum 1000 members.
func (c *Client) Members(guildID discord.Snowflake, limit uint,
after discord.Snowflake) ([]discord.Member, error) {
var param struct {
Limit uint `schema:"limit,omitempty"`
After discord.Snowflake `schema:"after,omitempty"`
}
param.Limit = limit
param.After = after
var mems []discord.Member
return mems, c.RequestJSON(&mems, "GET",
EndpointGuilds+guildID.String()+"/members")
return mems, c.RequestJSON(
&mems, "GET",
EndpointGuilds+guildID.String()+"/members",
httputil.WithSchema(c, param),
)
}
// AnyMemberData, all fields are optional.
@ -172,8 +190,8 @@ func (c *Client) GetBan(
EndpointGuilds+guildID.String()+"/bans/"+userID.String())
}
// Ban requires the BAN_MEMBERS permission. Days is the days back for Discord to
// delete the user's message, maximum 7 days.
// Ban requires the BAN_MEMBERS permission. Days is the days back for Discord
// to delete the user's message, maximum 7 days.
func (c *Client) Ban(
guildID, userID discord.Snowflake, days uint, reason string) error {
@ -182,8 +200,8 @@ func (c *Client) Ban(
}
var param struct {
DeleteDays uint `json:"delete_message_days,omitempty"`
Reason string `json:"reason,omitempty"`
DeleteDays uint `schema:"delete_message_days,omitempty"`
Reason string `schema:"reason,omitempty"`
}
param.DeleteDays = days
@ -192,7 +210,7 @@ func (c *Client) Ban(
return c.FastRequest(
"PUT",
EndpointGuilds+guildID.String()+"/bans/"+userID.String(),
httputil.WithJSONBody(c, param),
httputil.WithSchema(c, param),
)
}
@ -262,3 +280,175 @@ func (c *Client) DeleteRole(guildID, roleID discord.Snowflake) error {
return c.FastRequest("DELETE",
EndpointGuilds+guildID.String()+"/roles/"+roleID.String())
}
// PruneCount returns the number of members that would be removed in a prune
// operation. Requires KICK_MEMBERS. Days must be 1 or more, default 7.
func (c *Client) PruneCount(
guildID discord.Snowflake, days uint) (uint, error) {
if days == 0 {
days = 7
}
var param struct {
Days uint `schema:"days"`
}
param.Days = days
var resp struct {
Pruned uint `json:"pruned"`
}
return resp.Pruned, c.RequestJSON(
&resp, "GET",
EndpointGuilds+guildID.String()+"/prune",
httputil.WithSchema(c, param),
)
}
// Prune returns the number of members that is removed. Requires KICK_MEMBERS.
// Days must be 1 or more, default 7.
func (c *Client) Prune(
guildID discord.Snowflake, days uint) (uint, error) {
if days == 0 {
days = 7
}
var param struct {
Count uint `schema:"count"`
RetCount bool `schema:"compute_prune_count"`
}
param.Count = days
param.RetCount = true // maybe expose this later?
var resp struct {
Pruned uint `json:"pruned"`
}
return resp.Pruned, c.RequestJSON(
&resp, "POST",
EndpointGuilds+guildID.String()+"/prune",
httputil.WithSchema(c, param),
)
}
// GuildVoiceRegions is the same as /voice, but returns VIP ones as well if
// available.
func (c *Client) VoiceRegionsGuild(
guildID discord.Snowflake) ([]discord.VoiceRegion, error) {
var vrs []discord.VoiceRegion
return vrs, c.RequestJSON(&vrs, "GET",
EndpointGuilds+guildID.String()+"/regions")
}
// Integrations requires MANAGE_GUILD.
func (c *Client) Integrations(
guildID discord.Snowflake) ([]discord.Integration, error) {
var ints []discord.Integration
return ints, c.RequestJSON(&ints, "GET",
EndpointGuilds+guildID.String()+"/integrations")
}
// AttachIntegration requires MANAGE_GUILD.
func (c *Client) AttachIntegration(guildID, integrationID discord.Snowflake,
integrationType discord.IntegrationType) error {
var param struct {
Type discord.IntegrationType `json:"type"`
ID discord.Snowflake `json:"id"`
}
return c.FastRequest(
"POST",
EndpointGuilds+guildID.String()+"/integrations",
httputil.WithJSONBody(c, param),
)
}
// ModifyIntegration requires MANAGE_GUILD.
func (c *Client) ModifyIntegration(guildID, integrationID discord.Snowflake,
expireBehavior, expireGracePeriod int, emoticons bool) error {
var param struct {
ExpireBehavior int `json:"expire_behavior"`
ExpireGracePeriod int `json:"expire_grace_period"`
EnableEmoticons bool `json:"enable_emoticons"`
}
param.ExpireBehavior = expireBehavior
param.ExpireGracePeriod = expireGracePeriod
param.EnableEmoticons = emoticons
return c.FastRequest("PATCH", EndpointGuilds+guildID.String()+
"/integrations/"+integrationID.String(),
httputil.WithSchema(c, param),
)
}
func (c *Client) SyncIntegration(
guildID, integrationID discord.Snowflake) error {
return c.FastRequest("POST", EndpointGuilds+guildID.String()+
"/integrations/"+integrationID.String()+"/sync")
}
func (c *Client) GuildEmbed(
guildID discord.Snowflake) (*discord.GuildEmbed, error) {
var ge *discord.GuildEmbed
return ge, c.RequestJSON(&ge, "GET",
EndpointGuilds+guildID.String()+"/embed")
}
// ModifyGuildEmbed should be used with care: if you still want the embed
// enabled, you need to set the Enabled boolean, even if it's already enabled.
// If you don't, JSON will default it to false.
func (c *Client) ModifyGuildEmbed(guildID discord.Snowflake,
data discord.GuildEmbed) (*discord.GuildEmbed, error) {
return &data, c.RequestJSON(&data, "PATCH",
EndpointGuilds+guildID.String()+"/embed")
}
// GuildVanityURL returns *Invite, but only Code and Uses are filled. Requires
// MANAGE_GUILD.
func (c *Client) GuildVanityURL(
guildID discord.Snowflake) (*discord.Invite, error) {
var inv *discord.Invite
return inv, c.RequestJSON(&inv, "GET",
EndpointGuilds+guildID.String()+"/vanity-url")
}
type GuildImageType string
const (
GuildShield GuildImageType = "shield"
GuildBanner1 GuildImageType = "banner1"
GuildBanner2 GuildImageType = "banner2"
GuildBanner3 GuildImageType = "banner3"
GuildBanner4 GuildImageType = "banner4"
)
func (c *Client) GuildImageURL(
guildID discord.Snowflake, img GuildImageType) string {
return EndpointGuilds + guildID.String() +
"/widget.png?style=" + string(img)
}
func (c *Client) GuildImage(
guildID discord.Snowflake, img GuildImageType) (io.ReadCloser, error) {
r, err := c.Request("GET", c.GuildImageURL(guildID, img))
if err != nil {
return nil, err
}
return r.Body, nil
}

View file

@ -12,6 +12,7 @@ import (
var ErrInvalidImageCT = errors.New("Unknown image content-type")
var ErrInvalidImageData = errors.New("Invalid image data")
var ErrNoImage = errors.New("null image")
type ErrImageTooLarge struct {
Size, Max int
@ -101,6 +102,10 @@ var _ json.Marshaler = (*Image)(nil)
var _ json.Unmarshaler = (*Image)(nil)
func (i Image) MarshalJSON() ([]byte, error) {
if len(i.Content) == 0 {
return []byte("null"), nil
}
b, err := i.Encode()
if err != nil {
return nil, err
@ -113,6 +118,10 @@ func (i *Image) UnmarshalJSON(v []byte) error {
// Trim string
v = bytes.Trim(v, `"`)
if string(v) == "null" {
return ErrNoImage
}
img, err := DecodeImage(v)
if err != nil {
return err

View file

@ -2,6 +2,7 @@ package api
import (
"github.com/diamondburned/arikawa/discord"
"github.com/diamondburned/arikawa/httputil"
)
const EndpointInvites = Endpoint + "invites/"
@ -20,23 +21,38 @@ type MetaInvite struct {
func (c *Client) Invite(code string) (*discord.Invite, error) {
var params struct {
WithCounts bool `json:"with_counts,omitempty"`
WithCounts bool `schema:"with_counts,omitempty"`
}
// Nothing says I can't!
params.WithCounts = true
var inv *discord.Invite
return inv, c.RequestJSON(&inv, "GET", EndpointInvites+code)
return inv, c.RequestJSON(
&inv, "GET",
EndpointInvites+code,
httputil.WithSchema(c, params),
)
}
// Invites is only for guild channels.
func (c *Client) Invites(channelID discord.Snowflake) ([]discord.Invite, error) {
// ChannelInvites is only for guild channels. GuildInvites is for guilds.
func (c *Client) ChannelInvites(
channelID discord.Snowflake) ([]discord.Invite, error) {
var invs []discord.Invite
return invs, c.RequestJSON(&invs, "GET",
EndpointChannels+channelID.String()+"/invites")
}
// GuildInvites is for guilds.
func (c *Client) GuildInvites(
guildID discord.Snowflake) ([]discord.Invite, error) {
var invs []discord.Invite
return invs, c.RequestJSON(&invs, "GET",
EndpointGuilds+guildID.String()+"/invites")
}
// CreateInvite is only for guild channels. This endpoint requires
// CREATE_INSTANT_INVITE.
//
@ -44,24 +60,28 @@ func (c *Client) Invites(channelID discord.Snowflake) ([]discord.Invite, error)
// number of uses, 0 for unlimited. Temporary is whether this invite grants
// temporary membership. Unique, if true, tries not to reuse a similar invite,
// useful for creating unique one time use invites.
func (c *Client) CreateInvite(channelID discord.Snowflake,
maxAge discord.Seconds, maxUses uint, temp, unique bool) (*discord.Invite, error) {
func (c *Client) CreateInvite(
channelID discord.Snowflake, maxAge discord.Seconds,
maxUses uint, temp, unique bool) (*discord.Invite, error) {
var params struct {
var param struct {
MaxAge uint `json:"max_age"`
MaxUses uint `json:"max_uses"`
Temporary bool `json:"temporary"`
Unique bool `json:"unique"`
}
params.MaxAge = uint(maxAge)
params.MaxUses = maxUses
params.Temporary = temp
params.Unique = unique
param.MaxAge = uint(maxAge)
param.MaxUses = maxUses
param.Temporary = temp
param.Unique = unique
var inv *discord.Invite
return inv, c.RequestJSON(&inv, "POST",
EndpointChannels+channelID.String()+"/invites")
return inv, c.RequestJSON(
&inv, "POST",
EndpointChannels+channelID.String()+"/invites",
httputil.WithSchema(c, param),
)
}
// DeleteInvite requires either MANAGE_CHANNELS on the target channel, or

View file

@ -56,7 +56,7 @@ func (c *Client) messages(channelID discord.Snowflake,
var msgs []discord.Message
return msgs, c.RequestJSON(&msgs, "GET",
EndpointChannels+channelID.String(), httputil.WithJSONBody(c, body))
EndpointChannels+channelID.String(), httputil.WithSchema(c, body))
}
func (c *Client) Message(

View file

@ -36,20 +36,24 @@ func (c *Client) ReactionRange(
limit = 100
}
var query struct {
Before discord.Snowflake `json:"before,omitempty"`
After discord.Snowflake `json:"after,omitempty"`
var param struct {
Before discord.Snowflake `schema:"before,omitempty"`
After discord.Snowflake `schema:"after,omitempty"`
Limit uint `json:"limit"`
Limit uint `schema:"limit"`
}
param.Before = before
param.After = after
param.Limit = limit
var users []discord.User
var msgURL = EndpointChannels + chID.String() +
"/messages/" + msgID.String() +
"/reactions/" + emoji
return users, c.RequestJSON(&users, "GET", msgURL,
httputil.WithJSONBody(c, query))
httputil.WithSchema(c, param))
}
// DeleteReaction requires MANAGE_MESSAGES if not @me.

39
api/user.go Normal file
View file

@ -0,0 +1,39 @@
package api
import "github.com/diamondburned/arikawa/discord"
const EndpointUsers = Endpoint + "users/"
const EndpointMe = EndpointUsers + "@me"
func (c *Client) User(userID discord.Snowflake) (*discord.User, error) {
var u *discord.User
return u, c.RequestJSON(&u, "GET",
EndpointUsers+userID.String())
}
func (c *Client) Me() (*discord.User, error) {
var me *discord.User
return me, c.RequestJSON(&me, "GET", EndpointMe)
}
type ModifySelfData struct {
Username string `json:"username,omitempty"`
Avatar Image `json:"image,omitempty"`
}
func (c *Client) ModifyMe(data ModifySelfData) (*discord.User, error) {
var u *discord.User
return u, c.RequestJSON(&u, "PATCH", EndpointMe)
}
// Guilds returns maximum 100 of your guilds. To paginate, call MyGuildsRange.
// Guilds returned have some fields filled only (ID, Name, Icon, Owner,
// Permissions).
func (c *Client) Guilds() ([]discord.Guild, error) {
var gs []discord.Guild
return gs, c.RequestJSON(&gs, "GET", EndpointMe+"/guilds")
}
// func (c *Client) GuildsRange()
// func (c *Client)

View file

@ -80,24 +80,6 @@ type Presence struct {
RoleIDs []Snowflake `json:"roles"`
}
type VoiceState struct {
// GuildID isn't available from the Guild struct.
GuildID Snowflake `json:"guild_id,string"`
ChannelID Snowflake `json:"channel_id,string"`
UserID Snowflake `json:"user_id,string"`
Member *Member `json:"member,omitempty"`
SessionID string `json:"session_id"`
Deaf bool `json:"deaf"`
Mute bool `json:"mute"`
SelfDeaf bool `json:"self_deaf"`
SelfMute bool `json:"self_mute"`
SelfStream bool `json:"self_stream,omitempty"`
Suppress bool `json:"suppress"`
}
type Member struct {
User User `json:"user"`
Nick string `json:"nick,omitempty"`
@ -114,3 +96,31 @@ type Ban struct {
Reason string `json:"reason,omitempty"`
User User `json:"user"`
}
type Integration struct {
ID Snowflake `json:"id"`
Name string `json:"name"`
Type IntegrationType `json:"type"`
Enabled bool `json:"enabled"`
Syncing bool `json:"syncing"`
// used for subscribers
RoleID Snowflake `json:"role_id"`
ExpireBehavior int `json:"expire_behavior"`
ExpireGracePeriod int `json:"expire_grace_period"`
User User `json:"user"`
Account struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"account"`
SyncedAt Timestamp `json:"synced_at"`
}
type GuildEmbed struct {
Enabled bool `json:"enabled"`
ChannelID Snowflake `json:"channel_id,omitempty"`
}

View file

@ -85,3 +85,10 @@ const (
// number.
VeryHighVerification
)
type IntegrationType string
const (
TwitchIntegration IntegrationType = "twitch"
YouTubeIntegration IntegrationType = "youtube"
)

28
discord/voice.go Normal file
View file

@ -0,0 +1,28 @@
package discord
type VoiceState struct {
// GuildID isn't available from the Guild struct.
GuildID Snowflake `json:"guild_id,string"`
ChannelID Snowflake `json:"channel_id,string"`
UserID Snowflake `json:"user_id,string"`
Member *Member `json:"member,omitempty"`
SessionID string `json:"session_id"`
Deaf bool `json:"deaf"`
Mute bool `json:"mute"`
SelfDeaf bool `json:"self_deaf"`
SelfMute bool `json:"self_mute"`
SelfStream bool `json:"self_stream,omitempty"`
Suppress bool `json:"suppress"`
}
type VoiceRegion struct {
ID string `json:"id"`
Name string `json:"name"`
VIP bool `json:"vip"`
Optimal bool `json:"optimal"`
Deprecated bool `json:"deprecated"`
Custom bool `json:"custom"` // used for events
}

1
go.mod
View file

@ -4,5 +4,6 @@ go 1.13
require (
github.com/bwmarrin/discordgo v0.20.2
github.com/gorilla/schema v1.1.0
github.com/pkg/errors v0.8.1
)

2
go.sum
View file

@ -1,5 +1,7 @@
github.com/bwmarrin/discordgo v0.20.2 h1:nA7jiTtqUA9lT93WL2jPjUp8ZTEInRujBdx1C9gkr20=
github.com/bwmarrin/discordgo v0.20.2/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q=
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=

View file

@ -13,6 +13,7 @@ import (
type Client struct {
http.Client
json.Driver
SchemaEncoder
}
func NewClient() Client {
@ -20,7 +21,8 @@ func NewClient() Client {
Client: http.Client{
Timeout: 10 * time.Second,
},
Driver: json.Default{},
Driver: json.Default{},
SchemaEncoder: &DefaultSchema{},
}
}

View file

@ -16,16 +16,31 @@ func JSONRequest(r *http.Request) error {
return nil
}
func WithBody(body io.ReadCloser) func(*http.Request) error {
func WithSchema(schema SchemaEncoder, v interface{}) RequestOption {
return func(r *http.Request) error {
params, err := schema.Encode(v)
if err != nil {
return err
}
var qs = r.URL.Query()
for k, v := range params {
qs[k] = append(qs[k], v...)
}
r.URL.RawQuery = qs.Encode()
return nil
}
}
func WithBody(body io.ReadCloser) RequestOption {
return func(r *http.Request) error {
r.Body = body
return nil
}
}
func WithJSONBody(
json json.Driver, v interface{}) func(r *http.Request) error {
func WithJSONBody(json json.Driver, v interface{}) RequestOption {
if v == nil {
return func(*http.Request) error {
return nil

31
httputil/schema.go Normal file
View file

@ -0,0 +1,31 @@
package httputil
import (
"net/url"
"sync"
"github.com/gorilla/schema"
)
// SchemaEncoder expects the encoder to read the "schema" tags.
type SchemaEncoder interface {
Encode(src interface{}) (url.Values, error)
}
type DefaultSchema struct {
once sync.Once
*schema.Encoder
}
var _ SchemaEncoder = (*DefaultSchema)(nil)
func (d *DefaultSchema) Encode(src interface{}) (url.Values, error) {
if d.Encoder == nil {
d.once.Do(func() {
d.Encoder = schema.NewEncoder()
})
}
var v = url.Values{}
return v, d.Encoder.Encode(src, v)
}