Finish all guild related endpoints and add a guildID field to some types

This commit is contained in:
SeanOMik 2020-06-19 01:32:28 -05:00
parent 65344ad26c
commit caa20f2c9c
No known key found for this signature in database
GPG Key ID: FA4D55AC05268A88
8 changed files with 709 additions and 24 deletions

View File

@ -26,8 +26,16 @@ proc getGuild*(cache: Cache, id: snowflake): Guild =
if (guild.id == id): if (guild.id == id):
return guild return guild
return newGuild(sendRequest(endpoint("/guild/" & $id), HttpGet, defaultHeaders(), return newGuild(sendRequest(endpoint("/guilds/" & $id), HttpGet, defaultHeaders(),
id, RateLimitBucketType.guild)) id, RateLimitBucketType.guild))
proc getChannelGuild*(channel: Channel, cache: Cache): Guild = proc getChannelGuild*(channel: Channel, cache: Cache): Guild =
return cache.getGuild(channel.guildID) return cache.getGuild(channel.guildID)
#[ proc getGuildMember*(guild: Guild, memberID: snowflake, cache: Cache): GuildMember =
for index, members in cache.members:
if (members.id == id):
return guild
return newGuildMember(sendRequest(endpoint("/guilds/" & $guild.id & "/members/" & $memberID),
HttpGet, defaultHeaders(), id, RateLimitBucketType.guild)) ]#

View File

@ -31,18 +31,18 @@ type
parentID*: snowflake ## ID of the parent category for a channel parentID*: snowflake ## ID of the parent category for a channel
lastPinTimestamp*: string ## When the last pinned message was pinned lastPinTimestamp*: string ## When the last pinned message was pinned
ChannelModify* = ref object ChannelFields* = ref object
## Use this type to modify a channel by setting each fields. ## Use this type to modify or create a channel by setting each fields.
name*: Option[string] name*: Option[string]
`type`*: Option[ChannelType] `type`*: Option[ChannelType]
position*: Option[int]
topic*: Option[string] topic*: Option[string]
nsfw*: Option[bool]
rateLimitPerUser*: Option[int]
bitrate*: Option[int] bitrate*: Option[int]
userLimit*: Option[int] userLimit*: Option[int]
rateLimitPerUser*: Option[int]
position*: Option[int]
permissionOverwrites*: Option[seq[Permissions]] ## Explicit permission overwrites for members and roles. permissionOverwrites*: Option[seq[Permissions]] ## Explicit permission overwrites for members and roles.
parentID*: Option[snowflake] parentID*: Option[snowflake]
nsfw*: Option[bool]
Invite* = object Invite* = object
## Represents a code that when used, adds a user to a guild or group DM channel. ## Represents a code that when used, adds a user to a guild or group DM channel.
@ -140,14 +140,14 @@ proc sendMessage*(channel: Channel, content: string, tts: bool = false): Message
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})), channel.id, defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})), channel.id,
RateLimitBucketType.channel, messagePayload)) RateLimitBucketType.channel, messagePayload))
proc modifyChannel*(channel: Channel, modify: ChannelModify): Future[Channel] {.async.} = proc modifyChannel*(channel: Channel, modify: ChannelFields): Future[Channel] {.async.} =
## Modifies the channel. ## Modifies the channel.
## ##
## Examples: ## Examples:
## ##
## .. code-block:: nim ## .. code-block:: nim
## var chan = getChannel(703084913510973472) ## var chan = getChannel(703084913510973472)
## chan = chan.modifyChannel(ChannelModify(topic: some("This is the channel topic"))) ## chan = chan.modifyChannel(ChannelFields(topic: some("This is the channel topic")))
var modifyPayload = %*{} var modifyPayload = %*{}

View File

@ -1,6 +1,6 @@
import websocket, asyncdispatch, json, httpClient, eventdispatcher, strformat import websocket, asyncdispatch, json, httpClient, eventdispatcher, strformat
import eventhandler, streams, nimcordutils, discordobject, user, cache, clientobjects import eventhandler, streams, nimcordutils, discordobject, user, cache, clientobjects
import strutils, channel, options, message, emoji import strutils, channel, options, message, emoji, guild
const const
nimcordMajor = 0 nimcordMajor = 0
@ -104,12 +104,15 @@ proc startConnection*(client: DiscordClient) {.async.} =
proc newDiscordClient(tkn: string): DiscordClient = proc newDiscordClient(tkn: string): DiscordClient =
## Create a DiscordClient using a token. ## Create a DiscordClient using a token.
##
## Sets globalDiscordClient to the newly created client.
globalToken = tkn globalToken = tkn
var cac: Cache var cac: Cache
new(cac) new(cac)
result = DiscordClient(token: tkn, cache: cac) result = DiscordClient(token: tkn, cache: cac)
globalDiscordClient = result
var tokenStream = newFileStream("token.txt", fmRead) var tokenStream = newFileStream("token.txt", fmRead)
var tkn: string var tkn: string
@ -144,7 +147,7 @@ registerEventListener(EventType.evtMessageCreate, proc(bEvt: BaseEvent) =
var channel: Channel = event.message.getMessageChannel(event.client.cache) var channel: Channel = event.message.getMessageChannel(event.client.cache)
if (channel != nil): if (channel != nil):
discard channel.sendMessage("Modifing Channel!") discard channel.sendMessage("Modifing Channel!")
discard channel.modifyChannel(ChannelModify(topic: some(modifyTopic))) discard channel.modifyChannel(ChannelFields(topic: some(modifyTopic)))
elif (event.message.content.startsWith("?deleteChannel")): elif (event.message.content.startsWith("?deleteChannel")):
let channelID = getIDFromJson(event.message.content.substr(15)) let channelID = getIDFromJson(event.message.content.substr(15))
var channel: Channel = event.client.cache.getChannel(channelID) var channel: Channel = event.client.cache.getChannel(channelID)
@ -166,6 +169,9 @@ registerEventListener(EventType.evtMessageCreate, proc(bEvt: BaseEvent) =
let messages = channel.getMessages(MessagesGetRequest(limit: some(amount), before: some(event.message.id))) let messages = channel.getMessages(MessagesGetRequest(limit: some(amount), before: some(event.message.id)))
discard channel.bulkDeleteMessages(messages) discard channel.bulkDeleteMessages(messages)
elif (event.message.content.startsWith("?reactToMessage")): elif (event.message.content.startsWith("?reactToMessage")):
var guild: Guild
discard guild.createGuildRole(name = some("Gamer Role"), color = some(0xff0000))
var channel: Channel = event.message.getMessageChannel(event.client.cache) var channel: Channel = event.message.getMessageChannel(event.client.cache)
if (channel != nil): if (channel != nil):
let emojis = @[newEmoji("⏮️"), newEmoji("⬅️"), newEmoji("⏹️"), newEmoji("➡️"), newEmoji("⏭️")] let emojis = @[newEmoji("⏮️"), newEmoji("⬅️"), newEmoji("⏹️"), newEmoji("➡️"), newEmoji("⏭️")]

View File

@ -8,4 +8,6 @@ type DiscordClient* = ref object
ws*: AsyncWebSocket ws*: AsyncWebSocket
heartbeatInterval*: int heartbeatInterval*: int
heartbeatAcked*: bool heartbeatAcked*: bool
lastSequence*: int lastSequence*: int
var globalDiscordClient*: DiscordClient ## Global instance of the DiscordClient.

View File

@ -1,4 +1,6 @@
import json, discordobject, channel, member, options, nimcordutils, emoji, role, permission, httpcore import json, discordobject, channel, member, options, nimcordutils, emoji
import role, permission, httpcore, strformat, image, asyncdispatch, user
import permission
type type
VerificationLevel* = enum VerificationLevel* = enum
@ -78,6 +80,68 @@ type
approximateMemberCount*: int approximateMemberCount*: int
approximatePresenceCount*: int approximatePresenceCount*: int
GuildPreview* = ref object of DiscordObject
## Represents a guild review.
name*: string
icon*: string
splash*: string
discoverySplash*: string
emojis*: seq[Emoji]
features*: seq[string]
approximateMemberCount*: int
approximatePresenceCount*: int
description*: string
GuildBan* = ref object
## A guild ban.
reason*: string
user*: User
VoiceRegion* = ref object
## Voice region.
id*: string
name*: string
vip*: bool
optimal*: bool
deprecated*: bool
custom*: bool
IntegrationExpireBehavior* = enum
intExpireBehRemoveRole = 0,
intExpireBehKick = 1
IntegrationAccount* = ref object
id*: string
name*: string
Integration* = ref object of DiscordObject
name*: string
`type`*: string ## Integration type (twitch, youtube, etc)
enabled*: bool
syncing*: bool
roleID*: snowflake
enableEmoticons*: bool
expireBehavior*: IntegrationExpireBehavior
expireGracePeriod*: int
user*: User
account*: IntegrationAccount
syncedAt*: string
GuildWidget* = ref object
enabled*: bool
channelID*: snowflake
GuildWidgetStyle* = enum
guildWidgetStyleShield = "shield", ## Shield style widget with Discord icon and guild members online count.
guildWidgetStyleBanner1 = "banner1", ## Large image with guild icon, name and online count.
## "POWERED BY DISCORD" as the footer of the widget.
guildWidgetStyleBanner2 = "banner2", ## Smaller widget style with guild icon, name and online count.
## Split on the right with Discord logo.
guildWidgetStyleBanner3 = "banner3", ## Large image with guild icon, name and online count.
## In the footer, Discord logo on the left and "Chat Now" on the right.
guildWidgetStyleBanner4 = "banner4"
proc newGuild*(json: JsonNode): Guild {.inline.} = proc newGuild*(json: JsonNode): Guild {.inline.} =
## Parses a Guild type from json. ## Parses a Guild type from json.
var g = Guild( var g = Guild(
@ -113,7 +177,7 @@ proc newGuild*(json: JsonNode): Guild {.inline.} =
if (json.contains("permissions")): if (json.contains("permissions")):
g.permissions = newPermissions(json["permissions"]) g.permissions = newPermissions(json["permissions"])
for role in json["roles"]: for role in json["roles"]:
g.roles.add(newRole(role)) g.roles.add(newRole(role, g.id))
for emoji in json["emojis"]: for emoji in json["emojis"]:
g.emojis.add(newEmoji(emoji)) g.emojis.add(newEmoji(emoji))
#TODO features #TODO features
@ -130,7 +194,7 @@ proc newGuild*(json: JsonNode): Guild {.inline.} =
#TODO: voice_states #TODO: voice_states
if (json.contains("members")): if (json.contains("members")):
for member in json["members"]: for member in json["members"]:
g.members.insert(newGuildMember(member)) g.members.insert(newGuildMember(member, g.id))
if (json.contains("channels")): if (json.contains("channels")):
for channel in json["channels"]: for channel in json["channels"]:
g.channels.insert(newChannel(channel)) g.channels.insert(newChannel(channel))
@ -157,6 +221,7 @@ proc createGuild*(name: string, region: Option[string], icon: Option[string], ve
## Create a new guild. ## Create a new guild.
## ##
## Some restraints/notes for this endpoint: ## Some restraints/notes for this endpoint:
## * This endpoint is only available with bots that are in less than 10 guilds.
## * When using the roles parameter, the first member of the array is used ## * When using the roles parameter, the first member of the array is used
## to change properties of the guild's @everyone role. If you are trying to ## to change properties of the guild's @everyone role. If you are trying to
## bootstrap a guild with additional roles, keep this in mind. ## bootstrap a guild with additional roles, keep this in mind.
@ -231,4 +296,526 @@ proc createGuild*(name: string, region: Option[string], icon: Option[string], ve
return newGuild(sendRequest(endpoint("/guilds"), HttpPost, return newGuild(sendRequest(endpoint("/guilds"), HttpPost,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})), defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
0, RateLimitBucketType.global, json)) 0, RateLimitBucketType.global, json))
proc getGuildPreview*(guild: Guild): GuildPreview =
## Returns the guild preview object for the given id, even if the user is not in the guild.
## Only available for public guilds!
let json = sendRequest(endpoint(fmt("/guilds/{guild.id}/preview")), HttpPost,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild)
result = GuildPreview(
id: getIDFromJson(json["id"].getStr()),
name: json["name"].getStr(),
icon: json["icon"].getStr(),
splash: json["splash"].getStr(),
discoverySplash: json["discovery_splash"].getStr(),
approximateMemberCount: json["approximate_member_count"].getInt(),
approximatePresenceCount: json["approximate_presence_count"].getInt(),
description: json["description"].getStr()
)
for emoji in json["emojis"]:
result.emojis.add(newEmoji(emoji))
for feature in json["features"]:
result.features.add(feature.getStr())
type GuildModify* = ref object
## Use this type to modify a guild by setting each fields.
name*: Option[string]
region*: Option[string]
verificationLevel*: Option[VerificationLevel]
defaultMessageNotifications*: Option[MessageNotificationsLevel]
explicitContentFilter*: Option[ExplicitContentFilterLevel]
afkChannelID*: Option[snowflake]
afkTimeout*: Option[int]
icon*: Option[Image]
ownerID*: Option[snowflake]
splash*: Option[Image]
banner*: Option[Image]
systemChannelID*: Option[snowflake]
rulesChannelID*: Option[snowflake]
publicUpdatesChannelID*: Option[snowflake]
preferredLocale*: Option[string]
proc modifyGuild*(guild: Guild, modify: GuildModify): Future[Guild] {.async.} =
## Modifies the Guild.
##
## Examples:
##
## .. code-block:: nim
## var guild = getGuild(703084913510973472)
## guild = guild.modifyGuild(GuildModify(name: some("Epic Gamer Guild")))
var modifyPayload = %*{}
if (modify.name.isSome):
modifyPayload.add("name", %modify.name.get())
if (modify.region.isSome):
modifyPayload.add("region", %modify.region.get())
if (modify.verificationLevel.isSome):
modifyPayload.add("verification_level", %modify.verificationLevel.get())
if (modify.defaultMessageNotifications.isSome):
modifyPayload.add("default_message_notifications", %modify.defaultMessageNotifications.get())
if (modify.explicitContentFilter.isSome):
modifyPayload.add("explicit_content_filter", %modify.explicitContentFilter.get())
if (modify.afkChannelID.isSome):
modifyPayload.add("afk_channel_id", %modify.afkChannelID.get())
if (modify.afkTimeout.isSome):
modifyPayload.add("afk_timeout", %modify.afkTimeout.get())
if (modify.icon.isSome):
modifyPayload.add("icon", %modify.icon.get().imageToDataURI())
if (modify.ownerID.isSome):
modifyPayload.add("owner_id", %modify.ownerID.get())
if (modify.splash.isSome):
modifyPayload.add("splash", %modify.splash.get().imageToDataURI())
if (modify.banner.isSome):
modifyPayload.add("banner", %modify.banner.get().imageToDataURI())
if (modify.systemChannelID.isSome):
modifyPayload.add("system_channel_id", %modify.systemChannelID.get())
if (modify.rulesChannelID.isSome):
modifyPayload.add("rules_channel_id", %modify.rulesChannelID.get())
if (modify.publicUpdatesChannelID.isSome):
modifyPayload.add("public_updates_channel_id", %modify.publicUpdatesChannelID.get())
if (modify.preferredLocale.isSome):
modifyPayload.add("preferred_locale", %modify.preferredLocale.get())
return newGuild(sendRequest(endpoint("/guilds/" & $guild.id), HttpPatch,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, modifyPayload))
proc deleteGuild*(guild: Guild) {.async.} =
## Delete the guild. The bot must be the owner of the guild!
discard sendRequest(endpoint("/guilds/" & $guild.id), HttpDelete,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
proc requestGuildChannels*(guild: var Guild): seq[Channel] =
## Request all guild channels via Discord's REST API
## Only use this if for some reason, guild.channels is inaccurate!
##
## Also updates the guild's channels when called.
let json = sendRequest(endpoint("/guilds/" & $guild.id & "/channels"), HttpGet,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
for channel in json:
result.add(newChannel(channel))
guild.channels = result
proc createGuildChannel*(guild: var Guild, create: ChannelFields): Future[Channel] {.async.} =
## Creates a new guild channel.
## The name field must be set, if you dont a `Defect` exception will be raised.
## The created channel will be added to the guild's `channels` field.
##
## Examples:
##
## .. code-block:: nim
## let guild = getGuild(703084913510973472)
## let channel = waitFor guild.createGuildChannel(ChannelFields(name: some("Epic Gamer Channel")))
var createPayload = %*{}
# Make sure that the name is supplied since its required for this endpoint.
if (create.name.isSome):
createPayload.add("name", %create.name.get())
else:
raise newException(Defect, "You must have a channel name when creating it!")
if (create.`type`.isSome):
createPayload.add("type", %create.`type`.get())
if (create.position.isSome):
createPayload.add("position", %create.position.get())
if (create.topic.isSome):
createPayload.add("topic", %create.topic.get())
if (create.nsfw.isSome):
createPayload.add("nsfw", %create.nsfw.get())
if (create.rateLimitPerUser.isSome):
createPayload.add("rate_limit_per_user", %create.rateLimitPerUser.get())
if (create.bitrate.isSome):
createPayload.add("bitrate", %create.bitrate.get())
if (create.userLimit.isSome):
createPayload.add("user_limit", %create.userLimit.get())
if (create.permissionOverwrites.isSome):
var permOverwrites = parseJson("[]")
for perm in create.permissionOverwrites.get():
permOverwrites.add(perm.permissionsToJson())
createPayload.add("permission_overwrites", permOverwrites)
result = newChannel(sendRequest(endpoint("/guilds/" & $guild.id & "/channels"), HttpPost,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, createPayload))
proc modifyGuildChannelPositions*(guild: var Guild, channels: seq[Channel]) {.async.} =
## Modify the positions of a set of channel objects for the guild.
## The order is determined by the channel's `position` field
var jsonBody: JsonNode
for channel in channels:
jsonBody.add(%*{"id": channel.id, "position": channel.position})
discard sendRequest(endpoint("/guilds/" & $guild.id & "/channels"), HttpPatch,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, jsonBody)
proc getGuildMember*(guild: var Guild, memberID: snowflake): GuildMember =
## Get a guild member.
## This first checks `guild.members`, but if it doesn't exist there,
## it will be requested from Discord's REST API.
##
## If we end up requesting one, it will add it to `guild.members`
for member in guild.members:
if (member.id == memberID):
return member
result = newGuildMember(sendRequest(endpoint(fmt("/guilds/{guild.id}/members/{memberID}")),
HttpPatch, defaultHeaders(), guild.id, RateLimitBucketType.guild), guild.id)
guild.members.add(result)
# Would this endpoint be worth adding? https://discord.com/developers/docs/resources/guild#list-guild-members
# And what about this one? https://discord.com/developers/docs/resources/guild#list-guild-members
proc modifyCurrentUserNick*(guild: Guild, nick: string) {.async.} =
## Modifies the nickname of the current user in a guild.
discard sendRequest(endpoint(fmt("/guilds/{guild.id}/members/@me/nick")), HttpPatch,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, %*{"nick": nick})
proc kickGuildMember*(guild: Guild, member: GuildMember) {.async.} =
## Remove a member from a guild. Requires `KICK_MEMBERS` permission.
discard sendRequest(endpoint(fmt("/guilds/{guild.id}/members/{member.id}")), HttpDelete,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
proc getGuildBans*(guild: Guild): seq[GuildBan] =
## Get a list of guild bans. Requires the `BAN_MEMBERS` permission.
let json = sendRequest(endpoint(fmt("/guilds/{guild.id}/bans")), HttpGet,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
for ban in json:
result.add(GuildBan(
reason: json{"reason"}.getStr(),
user: newUser(json["user"])
))
proc getGuildBan*(guild: Guild, userID: snowflake): GuildBan =
## Returns a ban object for the given user or nil if the ban cannot be found.
## Requires the BAN_MEMBERS permission.
let response = sendRequest(endpoint(fmt("/guilds/{guild.id}/bans{userID}")), HttpGet,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
if not response.isNil():
return GuildBan(
reason: response{"reason"}.getStr(),
user: newUser(response["user"])
)
else:
return nil
proc banGuildMember*(guild: Guild, userID: snowflake, reason: Option[string] = none(string), deleteMessageDays: Option[int] = none(int)) {.async.} =
## Create a guild ban, and optionally delete previous messages sent by the
## banned user. Requires the BAN_MEMBERS permission.
var jsonBody: JsonNode
if (reason.isSome):
jsonBody.add("reason", %reason.get())
if (deleteMessageDays.isSome):
jsonBody.add("deleteMessageDays", %deleteMessageDays.get())
discard sendRequest(endpoint(fmt("/guilds/{guild.id}/bans/{userID}")), HttpPut,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, jsonBody)
proc unbanGuildMember*(guild: Guild, userID: snowflake) {.async.} =
## Remove the ban for a user. Requires the BAN_MEMBERS permissions.
discard sendRequest(endpoint(fmt("/guilds/{guild.id}/bans/{userID}")), HttpDelete,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
proc requestGuildRoles*(guild: Guild): seq[Role] =
## Request all guild roles via Discord's REST API
## Only use this if for some reason, guild.roles is inaccurate!
##
## Also updates the guild's roles when called.
let jsonBody = sendRequest(endpoint(fmt("/guilds/{guild.id}/roles")), HttpGet,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
for role in jsonBody:
result.add(newRole(role, guild.id))
guild.roles = result
proc createGuildRole*(guild: Guild, name: Option[string] = none(string), permissions: Option[Permissions] = none(Permissions),
color: Option[int] = none(int), hoist: Option[bool] = none(bool), mentionable: Option[bool] = none(bool)): Future[Role] {.async.} =
## Create a new role for the guild. Requires the `MANAGE_ROLES` permission.
##
## Example:
##
## .. code-block:: nim
## discard guild.createGuildRole(name = some("Gamer Role"), color = some(0xff0000))
var jsonBody: JsonNode
if (name.isSome):
jsonBody.add("name", %name)
if (permissions.isSome):
jsonBody.add("permissions", %permissions.get().allowPerms)
if (color.isSome):
jsonBody.add("color", %color)
if (hoist.isSome):
jsonBody.add("hoist", %hoist)
if (mentionable.isSome):
jsonBody.add("mentionable", %mentionable)
return newRole(sendRequest(endpoint(fmt("/guilds/{guild.id}/roles")), HttpPost,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, jsonBody), guild.id)
proc modifyGuildRolePositions*(guild: var Guild, roles: seq[Role]) {.async.} =
## Modify the positions of a set of role objects for the guild.
## The order is determined by the role's `position` field
var jsonBody: JsonNode
for role in roles:
jsonBody.add(%*{"id": role.id, "position": role.position})
discard sendRequest(endpoint("/guilds/" & $guild.id & "/roles"), HttpPatch,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, jsonBody)
proc getGuildPruneCount*(guild: Guild, days: int = 7, includedRoles: seq[snowflake] = @[]): int =
## Returns the number of members that would be removed in a prune operation.
## Requires the `KICK_MEMBERS` permission.
##
## By default, prune will not remove users with roles. You can optionally include
## specific roles in your prune by providing the `includedRoles` parameter. Any
## inactive user that has a subset of the provided role(s) will be counted in
## the prune and users with additional roles will not.
var url = endpoint(fmt("/guilds/{guild.id}/prune"))
if days != 7:
url &= "?days=" & $days
if includedRoles.len != 0:
# If the days field was also set, then we need to ad "&" to the url.
if days != 7:
url &= "&"
url &= "include_roles=" & ($includedRoles).substr(1)
let jsonBody = sendRequest(url, HttpGet, defaultHeaders(), guild.id, RateLimitBucketType.guild)
return jsonBody["pruned"].getInt()
proc beginGuildPrune*(guild: Guild, days: int = 7, computePruneCount: bool = false, includedRoles: seq[snowflake] = @[]): Future[Option[int]] {.async.} =
## Returns the number of members that would be removed in a prune operation.
## Requires the `KICK_MEMBERS` permission.
##
## If you specify `computePruneCount` the proc will return the amount of users
## that were pruned. Not recommended on large guilds!
##
## By default, prune will not remove users with roles. You can optionally include
## specific roles in your prune by providing the `includedRoles` parameter. Any
## inactive user that has a subset of the provided role(s) will be counted in
## the prune and users with additional roles will not.
var url = endpoint(fmt("/guilds/{guild.id}/prune"))
if days != 7:
url &= "?days=" & $days
if includedRoles.len != 0:
# If the days field was also set, then we need to add "&" to the url.
if days != 7:
url &= "&"
url &= "include_roles=" & ($includedRoles).substr(1)
if computePruneCount:
# If the days or includedRoles field was also set, then we need to add "&" to the url.
if days != 7 or includedRoles.len != 0:
url &= "&"
url &= "compute_prune_count=" & $computePruneCount
let jsonBody = sendRequest(url, HttpGet, defaultHeaders(), guild.id, RateLimitBucketType.guild)
if computePruneCount:
return some(jsonBody["pruned"].getInt())
#TODO: https://discord.com/developers/docs/resources/guild#get-guild-voice-regions
proc getGuildVoiceRegions*(guild: Guild): seq[VoiceRegion] =
## Returns a list of voice region objects for the guild.
let jsonBody = sendRequest(endpoint(fmt("/guilds/{guild.id}/regions")), HttpGet,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
for voiceRegion in jsonBody:
result.add(VoiceRegion(
id: jsonBody["id"].getStr(),
name: jsonBody["name"].getStr(),
vip: jsonBody["vip"].getBool(),
optimal: jsonBody["optimal"].getBool(),
deprecated: jsonBody["deprecated"].getBool(),
custom: jsonBody["custom"].getBool()
))
proc getGuildInvites*(guild: Guild): seq[Invite] =
## Returns a list of invite objects (with invite metadata) for the guild.
## Requires the `MANAGE_GUILD` permission.
let jsonBody = sendRequest(endpoint(fmt("/guilds/{guild.id}/invites")), HttpGet,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
for invite in jsonBody:
result.add(newInvite(invite))
proc getGuildIntegrations*(guild: Guild): seq[Integration] =
## Returns a list of integration objects for the guild. Requires the `MANAGE_GUILD` permission.
let jsonBody = sendRequest(endpoint(fmt("/guilds/{guild.id}/integrations")), HttpGet,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
for integration in jsonBody:
result.add(Integration(
id: getIDFromJson(jsonBody["id"].getStr()),
name: jsonBody["name"].getStr(),
`type`: jsonBody["type"].getStr(),
enabled: jsonBody["enabled"].getBool(),
syncing: jsonBody["syncing"].getBool(),
roleID: getIDFromJson(jsonBody["role_id"].getStr()),
enableEmoticons: jsonBody["enable_emoticons"].getBool(),
expireBehavior: IntegrationExpireBehavior(jsonBody["expire_behavior"].getInt()),
expireGracePeriod: jsonBody{"expire_grace_period"}.getInt(),
user: newUser(jsonBody["user"]),
account: IntegrationAccount(
id: jsonBody["account"]["id"].getStr(),
name: jsonBody["account"]["name"].getStr(),
),
syncedAt: jsonBody["synced_at"].getStr()
))
proc createGuildIntegration*(guild: Guild, `type`: string, id: string) {.async.} =
## Attach an integration object from the current user to the guild. Requires the `MANAGE_GUILD` permission.
let jsonBody = %* {
"type": `type`,
"id": id
}
discard sendRequest(endpoint("/guilds/" & $guild.id & "/integrations"), HttpPost,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, jsonBody)
proc modifyGuildIntegration*(guild: Guild, integration: var Integration,
expireBehavior: Option[IntegrationExpireBehavior] = none(IntegrationExpireBehavior),
expireGracePeriod: Option[int] = none(int), enableEmoticons: Option[bool] = none(bool)) {.async.} =
## Modify the behavior and settings of an integration object for the guild. Requires the `MANAGE_GUILD` permission.
##
## The changes are reflected to the given `integration`.
##
## Example:
##
## .. code-block:: nim
## discard integration.modifyGuildIntegration(enable_emoticons = true)
var modifyPayload = %*{}
if (expireBehavior.isSome):
modifyPayload.add("expire_behavior", %expireBehavior.get())
integration.expireBehavior = (expireBehavior.get())
if (expireGracePeriod.isSome):
modifyPayload.add("expire_grace_period", %expireGracePeriod.get())
integration.expireGracePeriod = expireGracePeriod.get()
if (enableEmoticons.isSome):
modifyPayload.add("enable_emoticons", %enableEmoticons.get())
integration.enableEmoticons = enableEmoticons.get()
discard sendRequest(endpoint(fmt("/guilds/{guild.id}/integration/{integration.id}")), HttpPatch,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, modifyPayload)
proc deleteGuildIntegration*(guild: Guild, integration: Integration) {.async.} =
## Delete the attached integration object for the guild. Requires the `MANAGE_GUILD` permission.
discard sendRequest(endpoint(fmt("/guilds/{guild.id}/integrations/{integration.id}")), HttpDelete,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
proc syncGuildIntegration*(guild: Guild, integration: Integration) {.async.} =
## Sync an integration. Requires the `MANAGE_GUILD` permission.
discard sendRequest(endpoint(fmt("/guilds/{guild.id}/integrations/{integration.id}")), HttpPost,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
proc getGuildWidget*(guild: Guild): GuildWidget =
## Returns the guild widget object. Requires the `MANAGE_GUILD` permission.
let jsonBody = sendRequest(endpoint(fmt("/guilds/{guild.id}/widget")), HttpGet,
defaultHeaders(), guild.id, RateLimitBucketType.guild)
return GuildWidget(enabled: jsonBody["enabled"].getBool(),
channelID: getIDFromJson(jsonBody{"channel_id"}.getStr()))
proc modifyGuildWidget*(guild: Guild, widget: var GuildWidget, enabled: bool, channelID: snowflake) {.async.} =
## Modify a guild widget object for the guild. Requires the `MANAGE_GUILD` permission.
widget.enabled = enabled
widget.channelID = channelID
let jsonBody = %* {
"enabled": enabled,
"channelID": channelID
}
discard sendRequest(endpoint(fmt("/guilds/{guild.id}/widget")), HttpPost,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, jsonBody)
proc getGuildVanityURL*(guild: Guild): Invite =
## Returns a partial invite object for guilds with that feature enabled. Requires the `MANAGE_GUILD` permission.
return newInvite(sendRequest(endpoint(fmt("/guilds/{guild.id}/vanity-url")), HttpGet,
defaultHeaders(), guild.id, RateLimitBucketType.guild))
proc getGuildWidgetImage*(guild: Guild, style: GuildWidgetStyle): string =
## Returns a url to this guild's widget image.
##
## Style types:
## * Shield: Shield style widget with Discord icon and guild members
## online count. [Example](https://discord.com/api/guilds/81384788765712384/widget.png?style=shield)
## * Banner 1: Large image with guild icon, name and online count.
## "POWERED BY DISCORD" as the footer of the widget. [Example](https://discord.com/api/guilds/81384788765712384/widget.png?style=banner1)
## * Banner 2: Smaller widget style with guild icon, name and online
## count. Split on the right with Discord logo. [Example](https://discord.com/api/guilds/81384788765712384/widget.png?style=banner2)
## * Banner 3: Large image with guild icon, name and online count.
## In the footer, Discord logo on the left and "Chat Now" on the right [Example](https://discord.com/api/guilds/81384788765712384/widget.png?style=banner3)
## * Banner 4: Large Discord logo at the top of the widget. Guild
## icon, name and online count in the middle portion of the widget
## and a "JOIN MY SERVER" button at the bottom. [Example](https://discord.com/api/guilds/81384788765712384/widget.png?style=banner4)
result = fmt("guilds/{guild.id}/widget.png")
case (style)
of (GuildWidgetStyle.guildWidgetStyleShield):
result &= "?style=shield"
of (GuildWidgetStyle.guildWidgetStyleBanner1):
result &= "?style=banner1"
of (GuildWidgetStyle.guildWidgetStyleBanner2):
result &= "?style=banner2"
of (GuildWidgetStyle.guildWidgetStyleBanner3):
result &= "?style=banner3"
of (GuildWidgetStyle.guildWidgetStyleBanner4):
result &= "?style=banner4"

View File

@ -1,4 +1,4 @@
import discordobject, user, json, role import discordobject, user, json, role, options, asyncdispatch, nimcordutils, httpcore, strformat, strutils
type GuildMember* = ref object of DiscordObject type GuildMember* = ref object of DiscordObject
## This type is a guild member. ## This type is a guild member.
@ -9,9 +9,9 @@ type GuildMember* = ref object of DiscordObject
premiumSince*: string ## When the user started boosting the guild. premiumSince*: string ## When the user started boosting the guild.
deaf*: bool ## Whether the user is deafened in voice channels. deaf*: bool ## Whether the user is deafened in voice channels.
mute*: bool ## Whether the user is muted in voice channels. mute*: bool ## Whether the user is muted in voice channels.
guildID*: snowflake ## The guild this member is in.
proc newGuildMember*(json: JsonNode, guild: snowflake): GuildMember {.inline.} =
proc newGuildMember*(json: JsonNode): GuildMember {.inline.} =
## Construct a GuildMember using json. ## Construct a GuildMember using json.
var member = GuildMember( var member = GuildMember(
nick: json{"nick"}.getStr(), nick: json{"nick"}.getStr(),
@ -19,14 +19,60 @@ proc newGuildMember*(json: JsonNode): GuildMember {.inline.} =
joinedAt: json["joined_at"].getStr(), joinedAt: json["joined_at"].getStr(),
premiumSince: json{"premium_since"}.getStr(), premiumSince: json{"premium_since"}.getStr(),
deaf: json["deaf"].getBool(), deaf: json["deaf"].getBool(),
mute: json["mute"].getBool() mute: json["mute"].getBool(),
guildID: guild
) )
if (json.contains("user")): if (json.contains("user")):
member.user = newUser(json["user"]) member.user = newUser(json["user"])
for role in json: for role in json:
member.roles.add(newRole(role)) member.roles.add(newRole(role, member.guildID))
return member return member
type GuildMemberModify* = ref object
nick: Option[string]
roles: Option[seq[snowflake]]
mute: Option[bool]
deaf: Option[bool]
channelID: Option[snowflake]
proc modifyGuildMember*(member: GuildMember, memberID: snowflake, modify: GuildMemberModify) {.async.} =
## Modify attributes of a guild member. If the `channel_id` is set to null,
## this will force the target user to be disconnected from voice.
##
## The member's new attributes will be reflected to `guild.members`.
var modifyPayload = %*{}
if (modify.nick.isSome):
modifyPayload.add("nick", %modify.nick.get())
if (modify.roles.isSome):
# Convert the roles array to a string representation and remove the `@`
# that is at the front of a conversion like this.
var rolesStr = ($modify.roles.get()).substr(1)
modifyPayload.add(parseJson(rolesStr))
if (modify.mute.isSome):
modifyPayload.add("mute", %modify.mute.get())
if (modify.deaf.isSome):
modifyPayload.add("deaf", %modify.deaf.get())
if (modify.channelID.isSome):
modifyPayload.add("channel_id", %modify.channelID.get())
discard sendRequest(endpoint(fmt("/guilds/{member.guildID}/members/{member.id}")), HttpPatch,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
member.guildID, RateLimitBucketType.guild, modifyPayload)
proc addGuildMemberRole*(member: GuildMember, roleID: snowflake) {.async.} =
## Adds a role to a guild member. Requires the `MANAGE_ROLES` permission.
discard sendRequest(endpoint(fmt("/guilds/{member.guildID}/members/{member.id}/roles/{roleID}")),
HttpPut, defaultHeaders(), member.guildID, RateLimitBucketType.guild)
proc removeGuildMemberRole*(member: GuildMember, roleID: snowflake) {.async.} =
## Remove's a role to a guild member. Requires the `MANAGE_ROLES` permission.
discard sendRequest(endpoint(fmt("/guilds/{member.guildID}/members/{member.id}/roles/{roleID}")),
HttpDelete, defaultHeaders(), member.guildID, RateLimitBucketType.guild)

View File

@ -94,7 +94,7 @@ proc newMessage*(messageJson: JsonNode): Message =
if (messageJson.contains("author")): if (messageJson.contains("author")):
msg.author = newUser(messageJson["author"]) msg.author = newUser(messageJson["author"])
if (messageJson.contains("member")): if (messageJson.contains("member")):
msg.member = newGuildMember(messageJson["member"]) msg.member = newGuildMember(messageJson["member"], msg.guildID)
if (messageJson.contains("mentions")): if (messageJson.contains("mentions")):
let mentionsJson = messageJson["mentions"].getElems() let mentionsJson = messageJson["mentions"].getElems()

View File

@ -1,4 +1,4 @@
import json, nimcordutils, discordobject, permission import json, nimcordutils, discordobject, permission, options, httpcore, asyncdispatch, strformat
type Role* = ref object of DiscordObject type Role* = ref object of DiscordObject
name*: string name*: string
@ -24,3 +24,39 @@ proc newRole*(json: JsonNode, guild: snowflake): Role =
uint(json["permissions"].getInt())) uint(json["permissions"].getInt()))
) )
proc modifyGuildRole*(role: var Role, name: Option[string] = none(string), permissions: Option[Permissions] = none(Permissions),
color: Option[int] = none(int), hoist: Option[bool] = none(bool), mentionable: Option[bool] = none(bool)): Future[Role] {.async.} =
## Modify a guild role. Requires the `MANAGE_ROLES` permission.
## The changes will reflect on the `role` object you supplied.
##
## Example:
##
## .. code-block:: nim
## discard role.modifyGuildRole(name = some("Gamer Role"), color = some(0xff0000))
var jsonBody: JsonNode
if (name.isSome):
jsonBody.add("name", %name)
if (permissions.isSome):
jsonBody.add("permissions", %permissions.get().allowPerms)
if (color.isSome):
jsonBody.add("color", %color)
if (hoist.isSome):
jsonBody.add("hoist", %hoist)
if (mentionable.isSome):
jsonBody.add("mentionable", %mentionable)
result = newRole(sendRequest(endpoint(fmt("/guilds/{role.guildID}/roles/{role.id}")), HttpPatch,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
role.guildID, RateLimitBucketType.guild, jsonBody), role.guildID)
role = result
proc deleteGuildRole*(role: Role) {.async.} =
## Delete a guild role. Requires the `MANAGE_ROLES` permission.
discard sendRequest(endpoint(fmt("/guilds/{role.guildID}/roles/{role.id}")), HttpDelete,
defaultHeaders(), role.guildID, RateLimitBucketType.guild)