Clean-up and Nim version fix.

This commit is contained in:
Avahe Kellenberger 2020-08-05 03:06:52 -04:00
parent a061c825ef
commit 38f138f0a8
19 changed files with 395 additions and 384 deletions

0
generate_docs.sh Normal file → Executable file
View File

View File

@ -4,4 +4,4 @@ description = "Discord API wrapper written in Nim. Inspired by DisC++, my othe
license = "MIT"
srcDir = "src"
requires "nim >= 1.0.0", "websocket >= 0.4.0 & <= 0.4.1"
requires "nim >= 1.2.0", "websocket >= 0.4.0 & <= 0.4.1"

View File

@ -1,17 +1,17 @@
import message, member, channel, guild, discordobject, nimcordutils, httpcore, user, tables
type Cache* = ref object
members*: Table[snowflake, GuildMember]
messages*: Table[snowflake, Message]
channels*: Table[snowflake, Channel]
guilds*: Table[snowflake, Guild]
members*: Table[Snowflake, GuildMember]
messages*: Table[Snowflake, Message]
channels*: Table[Snowflake, Channel]
guilds*: Table[Snowflake, Guild]
proc getChannel*(cache: var Cache, id: snowflake): Channel =
proc getChannel*(cache: var Cache, id: Snowflake): Channel =
## Get a channel object from the id.
##
## If for some reason the channel is not in cache, it gets requested via the
## Discord REST API.
if (cache.channels.hasKey(id)):
if cache.channels.hasKey(id):
return cache.channels[id]
result = newChannel(sendRequest(endpoint("/channels/" & $id), HttpGet, defaultHeaders(),
@ -25,12 +25,12 @@ proc getMessageChannel*(msg: Message, cache: var Cache): Channel =
## Discord REST API.
return cache.getChannel(msg.channelID)
proc getGuild*(cache: var Cache, id: snowflake): Guild =
proc getGuild*(cache: var Cache, id: Snowflake): Guild =
## Get a guild object from it's id.
##
## If for some reason the guild is not in cache, it gets requested via the
## Discord REST API.
if (cache.guilds.hasKey(id)):
if cache.guilds.hasKey(id):
return cache.guilds[id]
result = newGuild(sendRequest(endpoint("/guilds/" & $id), HttpGet, defaultHeaders(),
@ -44,18 +44,18 @@ proc getChannelGuild*(channel: Channel, cache: var Cache): Guild =
## Discord REST API.
return cache.getGuild(channel.guildID)
proc getUser*(cache: Cache, id: snowflake): User =
proc getUser*(cache: Cache, id: Snowflake): User =
## Get a user object from it's id.
##
## If for some reason the user is not in cache, it gets requested via the
## Discord REST API.
if (cache.members.hasKey(id)):
if cache.members.hasKey(id):
return cache.members[id].user
return newUser(sendRequest(endpoint("/users/" & $id), HttpGet, defaultHeaders()))
proc cacheGuildChannel*(cache: var Cache, guildID: snowflake, channel: Channel) =
proc cacheGuildChannel*(cache: var Cache, guildID: Snowflake, channel: Channel) =
## Adds a channel in cache.guilds[guildID].channels.
## Only used for internal library, dont touch!
var guild = cache.getGuild(guildID)
guild.channels.add(channel)
guild.channels.add(channel)

View File

@ -14,21 +14,21 @@ type
Channel* = ref object of DiscordObject
## Discord channel object.
`type`*: ChannelType ## The type of channel.
guildID*: snowflake ## The id of the guild.
guildID*: Snowflake ## The id of the guild.
position*: int ## Sorting position of the channel.
permissionOverwrites*: seq[Permissions] ## Explicit permission overwrites for members and roles.
name*: string ## The name of the channel (2-100 characters).
topic*: string ## The channel topic (0-1024 characters).
nsfw*: bool ## Whether the channel is nsfw.
lastMessageID*: snowflake ## The id of the last message sent in this channel (may not point to an existing or valid message).
lastMessageID*: Snowflake ## The id of the last message sent in this channel (may not point to an existing or valid message).
bitrate*: int ## The bitrate (in bits) of the voice channel.
userLimit*: int ## The user limit of the voice channel.
rateLimitPerUser*: int ## Amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages or manage_channel, are unaffected.
recipients*: seq[User] ## The recipients of the DM
icon*: string ## Icon hash
ownerID*: snowflake ## ID of the DM creator
applicationID*: snowflake ## Application id of the group DM creator if it is bot-created
parentID*: snowflake ## ID of the parent category for a channel
ownerID*: Snowflake ## ID of the DM creator
applicationID*: Snowflake ## Application id of the group DM creator if it is bot-created
parentID*: Snowflake ## ID of the parent category for a channel
lastPinTimestamp*: string ## When the last pinned message was pinned
ChannelFields* = ref object
@ -41,13 +41,13 @@ type
rateLimitPerUser*: Option[int]
position*: Option[int]
permissionOverwrites*: Option[seq[Permissions]] ## Explicit permission overwrites for members and roles.
parentID*: Option[snowflake]
parentID*: Option[Snowflake]
nsfw*: Option[bool]
Invite* = object
## Represents a code that when used, adds a user to a guild or group DM channel.
code*: string ## The invite code (unique ID)
guildID*: snowflake ## The guild this invite is for
guildID*: Snowflake ## The guild this invite is for
channel*: Channel ## The channel this invite is for
inviter*: User ## The user who created the invite
targetUser*: User ## The target user for this invite
@ -74,39 +74,39 @@ proc newChannel*(channel: JsonNode): Channel {.inline.} =
`type`: ChannelType(channel["type"].getInt())
)
if (channel.contains("guild_id")):
if channel.contains("guild_id"):
chan.guildID = getIDFromJson(channel["guild_id"].getStr())
if (channel.contains("position")):
if channel.contains("position"):
chan.position = channel["position"].getInt()
if (channel.contains("permission_overwrites")):
if channel.contains("permission_overwrites"):
for perm in channel["permission_overwrites"]:
chan.permissionOverwrites.add(newPermissions(perm))
if (channel.contains("name")):
if channel.contains("name"):
chan.name = channel["name"].getStr()
if (channel.contains("topic")):
if channel.contains("topic"):
chan.topic = channel["topic"].getStr()
if (channel.contains("nsfw")):
if channel.contains("nsfw"):
chan.nsfw = channel["nsfw"].getBool()
if (channel.contains("last_message_id")):
if channel.contains("last_message_id"):
chan.lastMessageID = getIDFromJson(channel["last_message_id"].getStr())
if (channel.contains("bitrate")):
if channel.contains("bitrate"):
chan.bitrate = channel["bitrate"].getInt()
if (channel.contains("user_limit")):
if channel.contains("user_limit"):
chan.userLimit = channel["user_limit"].getInt()
if (channel.contains("rate_limit_per_user")):
if channel.contains("rate_limit_per_user"):
chan.rateLimitPerUser = channel["rate_limit_per_user"].getInt()
if (channel.contains("recipients")):
if channel.contains("recipients"):
for recipient in channel["recipients"]:
chan.recipients.insert(newUser(recipient))
if (channel.contains("icon")):
if channel.contains("icon"):
chan.icon = channel["icon"].getStr()
if (channel.contains("owner_id")):
if channel.contains("owner_id"):
chan.ownerID = getIDFromJson(channel["owner_id"].getStr())
if (channel.contains("application_id")):
if channel.contains("application_id"):
chan.applicationID = getIDFromJson(channel["application_id"].getStr())
if (channel.contains("parent_id")):
if channel.contains("parent_id"):
chan.parentID = getIDFromJson(channel["parent_id"].getStr())
if (channel.contains("last_pin_timestamp")):
if channel.contains("last_pin_timestamp"):
chan.lastPinTimestamp = channel["last_pin_timestamp"].getStr()
return chan
@ -117,23 +117,23 @@ proc newInvite*(json: JsonNode): Invite {.inline.} =
code: json["code"].getStr(),
channel: newChannel(json["channel"])
)
if (json.contains("guild")):
if json.contains("guild"):
invite.guildID = getIDFromJson(json["guild"]["id"].getStr())
if (json.contains("target_user")):
if json.contains("target_user"):
invite.targetUser = newUser(json["target_user"])
if (json.contains("approximate_presence_count")):
if json.contains("approximate_presence_count"):
invite.approximatePresenceCount = json["approximate_presence_count"].getInt()
if (json.contains("approximate_member_count")):
if json.contains("approximate_member_count"):
invite.approximateMemberCount = json["approximate_member_count"].getInt()
if (json.contains("uses")):
if json.contains("uses"):
invite.uses = json["uses"].getInt()
if (json.contains("max_uses")):
if json.contains("max_uses"):
invite.maxUsers = json["max_uses"].getInt()
if (json.contains("max_age")):
if json.contains("max_age"):
invite.maxAge = json["max_age"].getInt()
if (json.contains("temporary")):
if json.contains("temporary"):
invite.temporary = json["temporary"].getBool()
if (json.contains("created_at")):
if json.contains("created_at"):
invite.createdAt = json["created_at"].getStr()
return invite
@ -142,10 +142,10 @@ proc sendMessage*(channel: Channel, content: string, tts: bool = false, embed: E
## Send a message through the channel.
var messagePayload = %*{"content": content, "tts": tts}
if (not embed.isNil()):
if not embed.isNil():
messagePayload.add("embed", embed.embedJson)
if (files.len != 0):
if files.len != 0:
var client = newHttpClient()
let endpoint = endpoint("/channels/" & $channel.id & "/messages")
var multipart = newMultipartData()
@ -154,7 +154,7 @@ proc sendMessage*(channel: Channel, content: string, tts: bool = false, embed: E
for index, file in files:
var imageStream = newFileStream(file.filePath, fmRead)
if (not isNil(imageStream)):
if not isNil(imageStream):
let data = imageStream.readALL()
multipart.add("file" & $index, data, file.fileName, "application/octet-stream", false)
@ -184,37 +184,37 @@ proc modifyChannel*(channel: Channel, modify: ChannelFields): Future[Channel] {.
var modifyPayload = %*{}
if (modify.name.isSome):
if modify.name.isSome:
modifyPayload.add("name", %modify.name.get())
if (modify.`type`.isSome):
if modify.`type`.isSome:
modifyPayload.add("type", %modify.`type`.get())
if (modify.position.isSome):
if modify.position.isSome:
modifyPayload.add("position", %modify.position.get())
if (modify.topic.isSome):
if modify.topic.isSome:
modifyPayload.add("topic", %modify.topic.get())
if (modify.nsfw.isSome):
if modify.nsfw.isSome:
modifyPayload.add("nsfw", %modify.nsfw.get())
if (modify.rateLimitPerUser.isSome):
if modify.rateLimitPerUser.isSome:
modifyPayload.add("rate_limit_per_user", %modify.rateLimitPerUser.get())
if (modify.bitrate.isSome):
if modify.bitrate.isSome:
modifyPayload.add("bitrate", %modify.bitrate.get())
if (modify.userLimit.isSome):
if modify.userLimit.isSome:
modifyPayload.add("user_limit", %modify.userLimit.get())
if (modify.permissionOverwrites.isSome):
if modify.permissionOverwrites.isSome:
var permOverwrites = parseJson("[]")
for perm in modify.permissionOverwrites.get():
permOverwrites.add(perm.permissionsToJson())
modifyPayload.add("permission_overwrites", permOverwrites)
if (modify.parentID.isSome):
if modify.parentID.isSome:
modifyPayload.add("parent_id", %modify.parentID.get())
return newChannel(sendRequest(endpoint("/channels/" & $channel.id), HttpPatch,
@ -229,9 +229,9 @@ proc deleteChannel*(channel: Channel) {.async.} =
type MessagesGetRequest* = object
## Use this type to get a channel's messages by setting some of the fields.
## You can only set one of `around`, `before`, or `after`.
around*: Option[snowflake]
before*: Option[snowflake]
after*: Option[snowflake]
around*: Option[Snowflake]
before*: Option[Snowflake]
after*: Option[Snowflake]
limit*: Option[int]
proc getMessages*(channel: Channel, request: MessagesGetRequest): seq[Message] =
@ -245,24 +245,24 @@ proc getMessages*(channel: Channel, request: MessagesGetRequest): seq[Message] =
var url: string = endpoint("/channels/" & $channel.id & "/messages?")
if (request.around.isSome):
if request.around.isSome:
url = url & "around=" & $request.around.get()
# Raise some exceptions to make sure the user doesn't
# try to set more than one of these fields
if (request.before.isSome):
if (request.around.isSome):
if request.before.isSome:
if request.around.isSome:
raise newException(Defect, "You cannot get around and before a message! Choose one...")
url = url & "before=" & $request.before.get()
if (request.after.isSome):
if (request.around.isSome or request.before.isSome):
if request.after.isSome:
if request.around.isSome or request.before.isSome:
raise newException(Defect, "You cannot get around/before and after a message! Choose one...")
url = url & "after=" & $request.after.get()
if (request.limit.isSome):
if request.limit.isSome:
# Add the `&` for the url if something else is set.
if (request.around.isSome or request.before.isSome or request.after.isSome):
if request.around.isSome or request.before.isSome or request.after.isSome:
url = url & "&"
url = url & "limit=" & $request.limit.get()
@ -273,15 +273,15 @@ proc getMessages*(channel: Channel, request: MessagesGetRequest): seq[Message] =
for message in response:
result.add(newMessage(message))
proc getMessage*(channel: Channel, messageID: snowflake): Message =
proc getMessage*(channel: Channel, messageID: Snowflake): Message =
## Requests a message from the channel via the Discord REST API.
return newMessage(sendRequest(endpoint("/channels/" & $channel.id & "/messages/" & $messageID), HttpGet,
defaultHeaders(), channel.id, RateLimitBucketType.channel))
proc bulkDeleteMessages*(channel: Channel, messageIDs: seq[snowflake]) {.async.} =
proc bulkDeleteMessages*(channel: Channel, messageIDs: seq[Snowflake]) {.async.} =
## Bulk delete channel messages. This endpoint can only delete 2-100 messages.
## This proc takes a seq[snowflakes] represtenting the message's IDs.
## This proc takes a seq[Snowflakes] represtenting the message's IDs.
## The messages can not be older than 2 weeks!
##
## See also:
@ -301,8 +301,8 @@ proc bulkDeleteMessages*(channel: Channel, messages: seq[Message]) {.async.} =
## The messages can not be older than 2 weeks!
##
## See also:
## * `bulkDeleteMessages(channel: Channel, messageIDs: seq[snowflake])`_
var messageIDs: seq[snowflake]
## * `bulkDeleteMessages(channel: Channel, messageIDs: seq[Snowflake])`_
var messageIDs: seq[Snowflake]
for msg in messages:
messageIDs.add(msg.id)
@ -329,7 +329,7 @@ type CreateInviteFields* = object
maxUses: Option[int] ## Max number of uses or 0 for unlimited
temporary: Option[bool] ## Whether this invite only grants temporary membership
unique: Option[bool] ## If true, don't try to reuse a similar invite (useful for creating many unique one time use invites)
targetUser: Option[snowflake] ## The target user id for this invite
targetUser: Option[Snowflake] ## The target user id for this invite
targetUserType: Option[int] ## The type of target user for this invite
proc createChannelInvite*(channel: Channel, fields: CreateInviteFields): Invite =
@ -344,18 +344,18 @@ proc createChannelInvite*(channel: Channel, fields: CreateInviteFields): Invite
## channel.createChannelInvite(CreateInviteFields(maxAge: 3600, maxUses: 10))
var createPayload = %*{}
if (fields.maxAge.isSome):
if fields.maxAge.isSome:
createPayload.add("max_age", %fields.maxAge.get())
if (fields.maxUses.isSome):
if fields.maxUses.isSome:
createPayload.add("max_uses", %fields.maxUses.get())
if (fields.temporary.isSome):
if fields.temporary.isSome:
createPayload.add("temporary", %fields.temporary.get())
if (fields.unique.isSome):
if fields.unique.isSome:
createPayload.add("unique", %fields.unique.get())
if (fields.targetUser.isSome):
if fields.targetUser.isSome:
createPayload.add("target_user", %fields.targetUser.get())
# Not sure if its needed because it can only be `1`
#[ if (fields.targetUserType.isSome):
#[ if fields.targetUserType.isSome:
createPayload.add("target_user_type", %fields.targetUserType.get()) ]#
return newInvite(sendRequest(endpoint("/channels/" & $channel.id & "/invites"), HttpPost,
@ -391,4 +391,4 @@ proc groupDMAddRecipient*(channel: Channel, user: User, accessToken: string, nic
proc groupDMRemoveRecipient*(channel: Channel, user: User) {.async.} =
## Removes a recipient from a Group DM.
discard sendRequest(endpoint("/channels/" & $channel.id & "/recipients/" & $user.id),
HttpPut, defaultHeaders(), channel.id, RateLimitBucketType.channel)
HttpPut, defaultHeaders(), channel.id, RateLimitBucketType.channel)

View File

@ -1,6 +1,6 @@
import websocket, asyncdispatch, json, httpClient, eventdispatcher, strformat
import eventhandler, streams, nimcordutils, discordobject, user, cache, clientobjects
import strutils, channel, options, message, emoji, guild, embed, os, presence
import nimcordutils, cache, clientobjects
import strutils, options, presence
type
DiscordOpCode = enum
@ -16,10 +16,23 @@ type
opHello = 10,
opHeartbeatAck = 11
# Forward declarations
proc closeConnection*(shard: Shard, code: int = 1000) {.async.}
proc getIdentifyPacket(shard: Shard): JsonNode
proc handleGatewayDisconnect(shard: Shard, error: string) {.async.}
proc handleHeartbeat(shard: Shard) {.async.}
proc handleWebsocketPacket(shard: Shard) {.async.}
proc newDiscordClient*(tkn: string): DiscordClient
proc newShard(shardID: int, client: DiscordClient): Shard
proc reconnectShard(shard: Shard) {.async.}
proc sendGatewayRequest*(shard: Shard, request: JsonNode, msg: string = "") {.async.}
proc startConnection*(client: DiscordClient, shardAmount: int = 1) {.async.}
proc updateClientPresence*(shard: Shard, presence: Presence) {.async.}
proc sendGatewayRequest*(shard: Shard, request: JsonNode, msg: string = "") {.async.} =
## Send a gateway request.
## Don't use this unless you know what you're doing!
if (msg.len == 0):
if msg.len == 0:
echo "Sending gateway payload: ", request
else:
echo msg
@ -29,7 +42,7 @@ proc sendGatewayRequest*(shard: Shard, request: JsonNode, msg: string = "") {.as
proc handleHeartbeat(shard: Shard) {.async.} =
while true:
var heartbeatPayload: JsonNode
if (shard.lastSequence == 0):
if shard.lastSequence == 0:
heartbeatPayload = %* { "d": nil, "op": ord(DiscordOpCode.opHeartbeat) }
else:
heartbeatPayload = %* { "d": shard.lastSequence, "op": ord(DiscordOpCode.opHeartbeat) }
@ -53,12 +66,9 @@ proc getIdentifyPacket(shard: Shard): JsonNode =
}
}
if (shard.client.shardCount != -1):
if shard.client.shardCount != -1:
result.add("shard", %*[shard.id, shard.client.shardCount])
# For some reason this shows as an error in VSCode, but it compiles fine.
#proc startConnection*(client: DiscordClient, shardCount: int = 1) {.async.}
proc closeConnection*(shard: Shard, code: int = 1000) {.async.} =
echo "Disconnecting with code: ", code
await shard.ws.close(code)
@ -73,7 +83,7 @@ proc reconnectShard(shard: Shard) {.async.} =
shard.reconnecting = false
shard.heartbeatAcked = true
#waitFor client.startConnection()
# waitFor client.startConnection()
# Handle discord disconnect. If it detects that we can reconnect, it will.
proc handleGatewayDisconnect(shard: Shard, error: string) {.async.} =
@ -87,15 +97,14 @@ proc handleGatewayDisconnect(shard: Shard, error: string) {.async.} =
let c = disconnectData.code
# 4003, 4004, 4005, 4007, 4010, 4011, 4012, 4013 are not reconnectable.
if ( (c >= 4003 and c <= 4005) or c == 4007 or (c >= 4010 and c <= 4013) ):
if (c >= 4003 and c <= 4005) or c == 4007 or (c >= 4010 and c <= 4013):
echo "The Discord gateway sent a disconnect code that we cannot reconnect to."
else:
if (not shard.reconnecting):
if not shard.reconnecting:
waitFor shard.reconnectShard()
else:
echo "Gateway is cannot reconnect due to already reconnecting..."
#TODO: Reconnecting may be done, just needs testing.
proc handleWebsocketPacket(shard: Shard) {.async.} =
while true:
@ -116,7 +125,7 @@ proc handleWebsocketPacket(shard: Shard) {.async.} =
echo "Failed to parse websocket payload: ", packet.data
continue
if (json.contains("s")):
if json.contains("s"):
shard.lastSequence = json["s"].getInt()
case json["op"].getInt()
@ -173,7 +182,7 @@ proc startConnection*(client: DiscordClient, shardAmount: int = 1) {.async.} =
## .. code-block:: nim
## var tokenStream = newFileStream("token.txt", fmRead)
## var tkn: string
## if (not isNil(tokenStream)):
## if not isNil(tokenStream):
## discard tokenStream.readLine(tkn)
## echo "Read token from the file: ", tkn
##
@ -182,13 +191,14 @@ proc startConnection*(client: DiscordClient, shardAmount: int = 1) {.async.} =
## var bot = newDiscordClient(tkn)
echo "Connecting..."
let urlResult = sendRequest(endpoint("/gateway/bot"), HttpMethod.HttpGet, defaultHeaders())
if (urlResult.contains("url")):
# let urlResult = sendRequest(endpoint("/gateway/bot"), HttpMethod.HttpGet, defaultHeaders())
let urlResult = sendRequest(endpoint("/gateway"), HttpMethod.HttpGet, defaultHeaders())
if urlResult.contains("url"):
let url = urlResult["url"].getStr()
client.endpoint = url
var shardCount = shardAmount
if (shardCount < urlResult["shards"].getInt()):
if urlResult.hasKey("shards") and shardCount < urlResult["shards"].getInt():
shardCount = urlResult["shards"].getInt()
client.shardCount = shardCount
@ -246,4 +256,4 @@ proc newDiscordClient*(tkn: string): DiscordClient =
var cac: Cache
new(cac)
result = DiscordClient(token: tkn, cache: cac)
result = DiscordClient(token: tkn, cache: cac)

View File

@ -1,7 +1,7 @@
type
snowflake* = uint64
Snowflake* = uint64
DiscordObject* = object of RootObj
id*: snowflake
id*: Snowflake
proc `==`*(obj1: DiscordObject, obj2: DiscordObject): bool =
return obj1.id == obj2.id
return obj1.id == obj2.id

View File

@ -12,10 +12,10 @@ proc setTitle*(embed: var Embed, title: string) =
##
## Contstraints:
## * `title` must be set and cannot be larger than 256 characters.
if (title.len < 0 or title.len > 256):
if title.len < 0 or title.len > 256:
raise newException(EmbedFieldException, "Embed title can only be 0-256 characters")
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
embed.embedJson.add("title", %title)
@ -25,17 +25,17 @@ proc setDescription*(embed: var Embed, description: string) =
##
## Contstraints:
## * `description` must be set and cannot be larger than 2048 characters.
if (description.len < 0 or description.len > 2048):
if description.len < 0 or description.len > 2048:
raise newException(EmbedFieldException, "Embed description can only be 0-2048 characters")
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
embed.embedJson.add("description", %description)
proc setURL*(embed: var Embed, url: string) =
## Set the url of the embed.
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
embed.embedJson.add("url", %url)
@ -43,14 +43,14 @@ proc setURL*(embed: var Embed, url: string) =
proc setTimestamp*(embed: var Embed, timestamp: string) =
## Set the timestamp of the embed.
## The timestamp is in `ISO8601` format.
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
embed.embedJson.add("timestamp", %timestamp)
proc setColor*(embed: var Embed, color: uint) =
## Set the color of the embed.
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
embed.embedJson.add("color", %color)
@ -62,7 +62,7 @@ proc setFooter*(embed: var Embed, text: string, iconURL: string = "", proxyIconU
##
## Contstraints:
## * `text` must be set and cannot be larger than 2048 characters.
if (text.len < 0 or text.len > 2048):
if text.len < 0 or text.len > 2048:
raise newException(EmbedFieldException, "Embed's footer text can only be 0-2048 characters")
let footer = %* {
@ -71,7 +71,7 @@ proc setFooter*(embed: var Embed, text: string, iconURL: string = "", proxyIconU
"proxy_icon_url": proxyIconURL
}
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
embed.embedJson.add("footer", footer)
@ -84,12 +84,12 @@ proc setImage*(embed: var Embed, url: string, proxyIconURL: string = "", height:
"proxy_icon_url": proxyIconURL
}
if (height != -1):
if height != -1:
image.add("height", %height)
if (width != -1):
if width != -1:
image.add("width", %width)
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
embed.embedJson.add("image", image)
@ -102,12 +102,12 @@ proc setThumbnail*(embed: var Embed, url: string, proxyIconURL: string = "", hei
"proxy_icon_url": proxyIconURL
}
if (height != -1):
if height != -1:
thumbnail.add("height", %height)
if (width != -1):
if width != -1:
thumbnail.add("width", %width)
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
embed.embedJson.add("thumbnail", thumbnail)
@ -118,12 +118,12 @@ proc setVideo*(embed: var Embed, url: string, height: int = -1, width: int = -1)
"url": url
}
if (height != -1):
if height != -1:
video.add("height", %height)
if (width != -1):
if width != -1:
video.add("width", %width)
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
embed.embedJson.add("video", video)
@ -135,7 +135,7 @@ proc setProvider*(embed: var Embed, name: string, url: string = "") =
"url": url
}
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
embed.embedJson.add("provider", provider)
@ -146,7 +146,7 @@ proc setAuthor*(embed: var Embed, name: string, url: string = "", iconURL: strin
##
## Contstraints:
## * `name` cannot be larger than 256 characters
if (name.len < 0 or name.len > 256):
if name.len < 0 or name.len > 256:
raise newException(EmbedFieldException, "Embed's author name can only be 0-256 characters")
let author = %* {
@ -156,7 +156,7 @@ proc setAuthor*(embed: var Embed, name: string, url: string = "", iconURL: strin
"proxy_icon_url": proxyIconURL
}
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
embed.embedJson.add("author", author)
@ -167,11 +167,11 @@ proc addField*(embed: var Embed, name: string, value: string, inline: bool = fal
## Contstraints:
## * `name` must be set and cannot be larger than 256 characters
## * `value` must be set and cannot be larger than 1024 characters
if (name.len == 0 or value.len == 0):
if name.len == 0 or value.len == 0:
raise newException(EmbedFieldException, "Embed's field name or values must be set")
elif (name.len > 256):
elif name.len > 256:
raise newException(EmbedFieldException, "Embed's field name can only be 0-256 characters")
elif (value.len > 1024):
elif value.len > 1024:
raise newException(EmbedFieldException, "Embed's field value can only be 0-1024 characters")
let field = %* {
@ -180,12 +180,12 @@ proc addField*(embed: var Embed, name: string, value: string, inline: bool = fal
"inline": inline
}
if (embed.embedJson.isNil()):
if embed.embedJson.isNil():
embed.embedJson = %*{}
if embed.embedJson.contains("fields"):
if (embed.embedJson["fields"].len > 25):
if embed.embedJson["fields"].len > 25:
raise newException(EmbedFieldException, "Embeds can only have upto 25 fields")
embed.embedJson["fields"].add(field)
else:
embed.embedJson.add("fields", %*[field])
embed.embedJson.add("fields", %*[field])

View File

@ -3,15 +3,15 @@ import json, discordobject, nimcordutils, user, httpcore, strutils, uri, strform
type
Emoji* = ref object of DiscordObject
name*: string
roles*: seq[snowflake]
roles*: seq[Snowflake]
user*: User
requireColons*: bool
managed*: bool
animated*: bool
available*: bool
guildID*: snowflake
guildID*: Snowflake
proc newEmoji*(json: JsonNode, guild: snowflake): Emoji =
proc newEmoji*(json: JsonNode, guild: Snowflake): Emoji =
## Construct an emoji with json.
## This shouldn't really be used by the user, only internal use.
result = Emoji(
@ -19,23 +19,23 @@ proc newEmoji*(json: JsonNode, guild: snowflake): Emoji =
guildID: guild
)
if (json.contains("id")):
if json.contains("id"):
result.id = getIDFromJson(json["id"].getStr())
if (json.contains("roles")):
if json.contains("roles"):
for role in json["roles"]:
result.roles.add(getIDFromJson(role.getStr()))
if (json.contains("user")):
if json.contains("user"):
result.user = newUser(json["user"])
if (json.contains("require_colons")):
if json.contains("require_colons"):
result.requireColons = json["require_colons"].getBool()
if (json.contains("managed")):
if json.contains("managed"):
result.managed = json["managed"].getBool()
if (json.contains("animated")):
if json.contains("animated"):
result.requireColons = json["animated"].getBool()
if (json.contains("available")):
if json.contains("available"):
result.requireColons = json["available"].getBool()
proc newEmoji*(name: string, id: snowflake): Emoji =
proc newEmoji*(name: string, id: Snowflake): Emoji =
## Construct an emoji using its name, and id.
return Emoji(name: name, id: id)
@ -49,7 +49,7 @@ proc `$`*(emoji: Emoji): string =
# Check if the emoji has a name but not id.
# If its true, this means that the emoji is just a unicode
# representation of the emoji.
if (emoji.id == 0 and not emoji.name.isEmptyOrWhitespace()):
if emoji.id == 0 and not emoji.name.isEmptyOrWhitespace():
return emoji.name
else:
result = $emoji.id & ":" & emoji.name
@ -61,13 +61,13 @@ proc `$`*(emoji: Emoji): string =
proc `==`*(a: Emoji, b: Emoji): bool =
## Check if two Emojis are equal.
# Check if emojis have name but no id
if (a.id == 0 and b.id == 0 and a.name.isEmptyOrWhitespace() and b.name.isEmptyOrWhitespace()):
if a.id == 0 and b.id == 0 and a.name.isEmptyOrWhitespace() and b.name.isEmptyOrWhitespace():
return a.name == b.name
# Check if emoji has IDs, but no name
elif (a.id != 0 and b.id != 0 and a.name.isEmptyOrWhitespace() and b.name.isEmptyOrWhitespace()):
elif a.id != 0 and b.id != 0 and a.name.isEmptyOrWhitespace() and b.name.isEmptyOrWhitespace():
return a.id == b.id
# Check if emoji has IDs, and a name
elif (a.id != 0 and b.id != 0 and not a.name.isEmptyOrWhitespace() and not b.name.isEmptyOrWhitespace()):
elif a.id != 0 and b.id != 0 and not a.name.isEmptyOrWhitespace() and not b.name.isEmptyOrWhitespace():
return $a == $b
return false
@ -77,7 +77,7 @@ proc toUrlEncoding*(emoji: Emoji): string =
## library use.
return encodeUrl($emoji, true)
proc modifyEmoji*(emoji: var Emoji, name: string, roles: seq[snowflake] = @[]): Future[Emoji] {.async.} =
proc modifyEmoji*(emoji: var Emoji, name: string, roles: seq[Snowflake]): Future[Emoji] {.async.} =
## Modify the given emoji. Requires the `MANAGE_EMOJIS` permission.
## Changes will be reflected in given `emoji`.
var jsonBody = %* {
@ -95,4 +95,4 @@ proc modifyEmoji*(emoji: var Emoji, name: string, roles: seq[snowflake] = @[]):
proc deleteEmoji*(emoji: Emoji) {.async.} =
## Delete the given emoji. Requires the `MANAGE_EMOJIS` permission.
discard sendRequest(endpoint(fmt("/guilds/{emoji.guildID}/emojis/{emoji.id}")), HttpDelete,
defaultHeaders(), emoji.guildID, RateLimitBucketType.guild)
defaultHeaders(), emoji.guildID, RateLimitBucketType.guild)

View File

@ -1,6 +1,6 @@
import eventhandler, json, tables, message, emoji, user, member, role
import guild, channel, nimcordutils, httpClient, strformat, cache
import sequtils, asyncdispatch, clientobjects, discordobject, presence
import asyncdispatch, clientobjects, discordobject, presence
proc readyEvent(shard: Shard, json: JsonNode) =
var readyEvent = ReadyEvent(shard: shard, readyPayload: json, name: $EventType.evtReady)
@ -26,7 +26,7 @@ proc channelCreateEvent(shard: Shard, json: JsonNode) =
let channelCreateEvent = ChannelCreateEvent(shard: shard, channel: chnl, name: $EventType.evtChannelCreate)
# Add the channel to its guild's `channels` field
if (chnl.guildID != 0):
if chnl.guildID != 0:
shard.client.cache.cacheGuildChannel(chnl.guildID, chnl)
shard.client.cache.channels[chnl.id] = chnl
@ -38,15 +38,15 @@ proc channelUpdateEvent(shard: Shard, json: JsonNode) =
shard.client.cache.channels[chnl.id] = chnl
if (chnl.guildID != 0):
if chnl.guildID != 0:
let g = shard.client.cache.getGuild(chnl.guildID)
var index = -1
for i, channel in g.channels:
if (channel.id == chnl.id):
if channel.id == chnl.id:
index = i
if (index != -1):
if index != -1:
g.channels[index] = chnl
else:
g.channels.add(chnl)
@ -68,7 +68,7 @@ proc channelPinsUpdate(shard: Shard, json: JsonNode) =
let channelID = getIDFromJson(json["channel_id"].getStr())
var channel: Channel
if (shard.client.cache.channels.hasKey(channelID)):
if shard.client.cache.channels.hasKey(channelID):
channel = shard.client.cache.channels[channelID]
channel.lastPinTimestamp = json["last_pin_timestamp"].getStr()
@ -194,15 +194,15 @@ proc guildMembersChunk(shard: Shard, json: JsonNode) =
event.chunkIndex = json["chunk_index"].getInt()
event.chunkCount = json["chunk_count"].getInt()
if (json.contains("not_found")):
if json.contains("not_found"):
for id in json["not_found"]:
event.notFound.add(getIDFromJson(id.getStr()))
if (json.contains("presences")):
if json.contains("presences"):
for presence in json["presences"]:
event.presences.add(newPresence(presence))
if (json.contains("nonce")):
if json.contains("nonce"):
event.nonce = json["nonce"].getStr()
dispatchEvent(event)
@ -252,7 +252,7 @@ proc inviteCreate(shard: Shard, json: JsonNode) =
invite.channel = shard.client.cache.getChannel(getIDFromJson(json["channel_id"].getStr()))
if (json.contains("guild_id")):
if json.contains("guild_id"):
invite.guildID =getIDFromJson(json["guild_id"].getStr())
var event = InviteCreateEvent(shard: shard, invite: invite, name: $EventType.evtInviteCreate)
@ -264,7 +264,7 @@ proc inviteDelete(shard: Shard, json: JsonNode) =
event.channel = shard.client.cache.getChannel(getIDFromJson(json["channel_id"].getStr()))
event.code = json["code"].getStr()
if (json.contains("guild_id")):
if json.contains("guild_id"):
event.guild = shard.client.cache.getGuild(getIDFromJson(json["guild_id"].getStr()))
dispatchEvent(event)
@ -295,7 +295,7 @@ proc messageDeleteEvent(shard: Shard, json: JsonNode) =
msg = Message(id: msgID)
msg.channelID = getIDFromJson(json["channel_id"].getStr())
if (json.contains("guild_id")):
if json.contains("guild_id"):
msg.guildID = getIDFromJson(json["guild_id"].getStr())
let event = MessageDeleteEvent(shard: shard, message: msg, name: $EventType.evtMessageDelete)
@ -305,7 +305,7 @@ proc messageDeleteBulkEvent(shard: Shard, json: JsonNode) =
var event = MessageDeleteBulkEvent(shard: shard, name: $EventType.evtMessageDeleteBulk)
event.channel = shard.client.cache.getChannel(getIDFromJson(json["channel_id"].getStr()))
if (json.contains("guild_id")):
if json.contains("guild_id"):
event.guild = shard.client.cache.getGuild(getIDFromJson(json["guild_id"].getStr()))
for msgIDJson in json["ids"]:
@ -319,7 +319,7 @@ proc messageDeleteBulkEvent(shard: Shard, json: JsonNode) =
msg = Message(id: msgID, channelID: channelID)
event.channel = shard.client.cache.getChannel(msg.channelID)
if (json.contains("guild_id")):
if json.contains("guild_id"):
msg.guildID = getIDFromJson(json["guild_id"].getStr())
event.messages.add(msg)
@ -337,12 +337,12 @@ proc messageReactionAdd(shard: Shard, json: JsonNode) =
msg = Message(id: msgID)
msg.channelID = getIDFromJson(json["channel_id"].getStr())
if (json.contains("guild_id")):
if json.contains("guild_id"):
msg.guildID = getIDFromJson(json["guild_id"].getStr())
event.user = shard.client.cache.getUser(getIDFromJson(json["user_id"].getStr()))
if (json.contains("member")):
if json.contains("member"):
event.member = newGuildMember(json["member"], msg.guildID)
event.emoji = newEmoji(json["emoji"], msg.guildID)
@ -360,7 +360,7 @@ proc messageReactionRemove(shard: Shard, json: JsonNode) =
msg = Message(id: msgID)
msg.channelID = getIDFromJson(json["channel_id"].getStr())
if (json.contains("guild_id")):
if json.contains("guild_id"):
msg.guildID = getIDFromJson(json["guild_id"].getStr())
event.user = shard.client.cache.getUser(getIDFromJson(json["user_id"].getStr()))
@ -380,7 +380,7 @@ proc messageReactionRemoveAll(shard: Shard, json: JsonNode) =
msg = Message(id: msgID)
msg.channelID = getIDFromJson(json["channel_id"].getStr())
if (json.contains("guild_id")):
if json.contains("guild_id"):
msg.guildID = getIDFromJson(json["guild_id"].getStr())
dispatchEvent(event)
@ -396,7 +396,7 @@ proc messageReactionRemoveEmoji(shard: Shard, json: JsonNode) =
msg = Message(id: msgID)
msg.channelID = getIDFromJson(json["channel_id"].getStr())
if (json.contains("guild_id")):
if json.contains("guild_id"):
msg.guildID = getIDFromJson(json["guild_id"].getStr())
event.emoji = newEmoji(json["emoji"], msg.guildID)
@ -414,9 +414,9 @@ proc presenceUpdate(shard: Shard, json: JsonNode) =
for role in json["roles"]:
member.roles.add(getIDFromJson(role.getStr()))
if (json.contains("premium_since")):
if json.contains("premium_since"):
member.premiumSince = json["premium_since"].getStr()
if (json.contains("nick")):
if json.contains("nick"):
member.nick = json["nick"].getStr()
member.presence = newPresence(json)
@ -426,12 +426,12 @@ proc typingStart(shard: Shard, json: JsonNode) =
event.channel = shard.client.cache.getChannel(getIDFromJson(json["channel_id"].getStr()))
if (json.contains("guild_id")):
if json.contains("guild_id"):
event.channel.guildID = getIDFromJson(json["guild_id"].getStr())
event.user = shard.client.cache.getUser(getIDFromJson(json["user_id"].getStr()))
if (json.contains("member")):
if json.contains("member"):
event.member = newGuildMember(json["member"], event.channel.guildID)
dispatchEvent(event)
@ -504,9 +504,9 @@ let internalEventTable: Table[string, proc(shard: Shard, json: JsonNode) {.nimca
proc handleDiscordEvent*(shard: Shard, json: JsonNode, eventName: string) {.async.} =
## Handles, and dispatches, a gateway event. Only used internally.
if (internalEventTable.hasKey(eventName)):
if internalEventTable.hasKey(eventName):
let eventProc: proc(shard: Shard, json: JsonNode) = internalEventTable[eventName]
eventProc(shard, json)
else:
echo "Failed to find event: ", eventName

View File

@ -130,7 +130,7 @@ type
members*: seq[GuildMember]
chunkIndex*: int
chunkCount*: int
notFound*: seq[snowflake]
notFound*: seq[Snowflake]
presences*: seq[Presence]
nonce*: string
@ -252,7 +252,7 @@ proc registerEventListener*(event: EventType, listener: proc(event: BaseEvent))
## echo "ID: ", bot.clientUser.id
## echo "--------------------"
## )
if (eventListeners.hasKey($event)):
if eventListeners.hasKey($event):
eventListeners[$event].add(cast[proc(event: BaseEvent)](listener))
echo "Added other event listener: ", $event
@ -264,10 +264,10 @@ proc registerEventListener*(event: EventType, listener: proc(event: BaseEvent))
proc dispatchEvent*[T: BaseEvent](event: T) =
## Dispatches an event so something can listen to it.
if (eventListeners.hasKey(event.name)):
if eventListeners.hasKey(event.name):
let listeners = eventListeners[event.name]
echo "Dispatching event: ", event.name
for index, eventListener in listeners.pairs:
eventListener(event)
else:
echo "No event listeners for event: ", event.name
echo "No event listeners for event: ", event.name

View File

@ -53,9 +53,9 @@ type
VoiceState* = ref object
## Used to represent a user's voice connection status
guildID*: snowflake
channelID*: snowflake
userID*: snowflake
guildID*: Snowflake
channelID*: Snowflake
userID*: Snowflake
member*: GuildMember
sessionID*: string
deaf*: bool
@ -72,10 +72,10 @@ type
splash*: string
discoverySplash*: string
owner*: bool
ownerID: snowflake
ownerID: Snowflake
permissions*: Permissions
region*: string
afkChannelID*: snowflake
afkChannelID*: Snowflake
afkTimeout*: int
verificationLevel*: VerificationLevel
defaultMessageNotifications*: MessageNotificationsLevel
@ -84,12 +84,12 @@ type
emojis*: seq[Emoji]
features*: seq[string]
mfaLevel*: MFALevel
applicationID*: snowflake
applicationID*: Snowflake
widgetEnabled*: bool
widgetChannelID*: snowflake
systemChannelID*: snowflake
widgetChannelID*: Snowflake
systemChannelID*: Snowflake
systemChannelFlags*: int
rulesChannelID*: snowflake
rulesChannelID*: Snowflake
joinedAt*: string
large*: bool
unavailable*: bool
@ -106,7 +106,7 @@ type
premiumTier*: PremiumTier
premiumSubscriptionCount*: int
preferredLocale*: string
publicUpdatesChannelID*: snowflake
publicUpdatesChannelID*: Snowflake
maxVideoChannelUsers*: int
approximateMemberCount*: int
approximatePresenceCount*: int
@ -150,7 +150,7 @@ type
`type`*: string ## Integration type (twitch, youtube, etc)
enabled*: bool
syncing*: bool
roleID*: snowflake
roleID*: Snowflake
enableEmoticons*: bool
expireBehavior*: IntegrationExpireBehavior
expireGracePeriod*: int
@ -160,7 +160,7 @@ type
GuildWidget* = ref object
enabled*: bool
channelID*: snowflake
channelID*: Snowflake
GuildWidgetStyle* = enum
guildWidgetStyleShield = "shield",
@ -199,9 +199,9 @@ proc newGuild*(json: JsonNode): Guild {.inline.} =
)
# Parse all non guaranteed fields
if (json.contains("owner")):
if json.contains("owner"):
g.owner = json["owner"].getBool()
if (json.contains("permissions")):
if json.contains("permissions"):
g.permissions = newPermissions(json["permissions"])
for role in json["roles"]:
g.roles.add(newRole(role, g.id))
@ -209,17 +209,17 @@ proc newGuild*(json: JsonNode): Guild {.inline.} =
g.emojis.add(newEmoji(emoji, g.id))
for feature in json["features"]:
g.features.add(feature.getStr())
if (json.contains("widget_enabled")):
if json.contains("widget_enabled"):
g.widgetEnabled = json["widget_enabled"].getBool()
if (json.contains("widget_channel_id")):
if json.contains("widget_channel_id"):
g.widgetChannelID = getIDFromJson(json["widget_channel_id"].getStr())
if (json.contains("large")):
if json.contains("large"):
g.large = json["large"].getBool()
if (json.contains("unavailable")):
if json.contains("unavailable"):
g.unavailable = json["unavailable"].getBool()
if (json.contains("member_count")):
if json.contains("member_count"):
g.memberCount = json["member_count"].getInt()
if (json.contains("voice_states")):
if json.contains("voice_states"):
for voicestate in json["voice_states"]:
var state = VoiceState(
guildID: g.id,
@ -234,47 +234,47 @@ proc newGuild*(json: JsonNode): Guild {.inline.} =
suppress: voicestate["suppress"].getBool()
)
if (voicestate.contains("member")):
if voicestate.contains("member"):
state.member = newGuildMember(voicestate["member"], g.id)
g.voiceStates.add(state)
if (json.contains("members")):
if json.contains("members"):
for member in json["members"]:
g.members.insert(newGuildMember(member, g.id))
if (json.contains("channels")):
if json.contains("channels"):
for channel in json["channels"]:
g.channels.insert(newChannel(channel))
if (json.contains("presences")):
if json.contains("presences"):
# Parse all presences
var tmpPresences = initTable[snowflake, Presence]()
var tmpPresences = initTable[Snowflake, Presence]()
for presence in json["presences"]:
tmpPresences.add(getIDFromJson(presence["user"]["id"].getStr()), newPresence(presence))
# Check if the `tmpPresences` variable has a presence for the member,
# if it does, then update the member to include its presence.
for member in g.members:
if (tmpPresences.hasKey(member.user.id)):
if tmpPresences.hasKey(member.user.id):
member.presence = tmpPresences[member.user.id]
if (json.contains("max_presences")):
if json.contains("max_presences"):
g.maxPresences = json["max_presences"].getInt()
if (json.contains("max_members")):
if json.contains("max_members"):
g.maxMembers = json["max_members"].getInt()
if (json.contains("premium_subscription_count")):
if json.contains("premium_subscription_count"):
g.premiumSubscriptionCount = json["premium_subscription_count"].getInt()
if (json.contains("max_video_channel_users")):
if json.contains("max_video_channel_users"):
g.maxVideoChannelUsers = json["max_video_channel_users"].getInt()
if (json.contains("approximate_member_count")):
if json.contains("approximate_member_count"):
g.approximateMemberCount = json["approximate_member_count"].getInt()
if (json.contains("approximate_presence_count")):
if json.contains("approximate_presence_count"):
g.approximatePresenceCount = json["approximate_presence_count"].getInt()
return g
proc createGuild*(name: string, region: Option[string], icon: Option[string], verificationLevel: Option[VerificationLevel],
defaultMessageNotifications: Option[MessageNotificationsLevel], explicitContentFilter: Option[ExplicitContentFilterLevel],
roles: Option[seq[Role]], channels: Option[seq[Channel]], afkChannelID: Option[snowflake], afkTimeout: Option[int],
systemChannelID: Option[snowflake]): Guild =
roles: Option[seq[Role]], channels: Option[seq[Channel]], afkChannelID: Option[Snowflake], afkTimeout: Option[int],
systemChannelID: Option[Snowflake]): Guild =
## Create a new guild.
##
## Some restraints/notes for this endpoint:
@ -296,17 +296,17 @@ proc createGuild*(name: string, region: Option[string], icon: Option[string], ve
var json = %* {"name": name}
if (region.isSome):
if region.isSome:
json.add("region", %region.get())
if (icon.isSome):
if icon.isSome:
json.add("icon", %icon.get())
if (verificationLevel.isSome):
if verificationLevel.isSome:
json.add("verification_level", %ord(verificationLevel.get()))
if (defaultMessageNotifications.isSome):
if defaultMessageNotifications.isSome:
json.add("default_message_notifications", %ord(defaultMessageNotifications.get()))
if (explicitContentFilter.isSome):
if explicitContentFilter.isSome:
json.add("explicit_content_filter", %ord(explicitContentFilter.get()))
if (roles.isSome):
if roles.isSome:
#json.add("verification_level", %ord(verificationLevel.get()))
var rolesJson = parseJson("[]")
for role in roles.get():
@ -322,7 +322,7 @@ proc createGuild*(name: string, region: Option[string], icon: Option[string], ve
rolesJson.add(roleJson)
json.add("channels", rolesJson)
if (channels.isSome):
if channels.isSome:
var channelsJson = parseJson("[]")
for channel in channels.get():
var channelJson = %*{
@ -336,7 +336,7 @@ proc createGuild*(name: string, region: Option[string], icon: Option[string], ve
"parent_id": channel.parentID
}
if (channel.permissionOverwrites.len != 0):
if channel.permissionOverwrites.len != 0:
channelsJson.add("permission_overwrites", parseJson("[]"))
for permOverwrite in channel.permissionOverwrites:
channelsJson["permission_overwrites"].add(permOverwrite.permissionsToJson())
@ -344,11 +344,11 @@ proc createGuild*(name: string, region: Option[string], icon: Option[string], ve
channelsJson.add(channelJson)
json.add("channels", channelsJson)
if (afkChannelID.isSome):
if afkChannelID.isSome:
json.add("afk_channel_id", %ord(afkChannelID.get()))
if (afkTimeout.isSome):
if afkTimeout.isSome:
json.add("afk_timeout", %ord(afkTimeout.get()))
if (systemChannelID.isSome):
if systemChannelID.isSome:
json.add("system_channel_id", %ord(systemChannelID.get()))
return newGuild(sendRequest(endpoint("/guilds"), HttpPost,
@ -386,15 +386,15 @@ type GuildModify* = ref object
verificationLevel*: Option[VerificationLevel]
defaultMessageNotifications*: Option[MessageNotificationsLevel]
explicitContentFilter*: Option[ExplicitContentFilterLevel]
afkChannelID*: Option[snowflake]
afkChannelID*: Option[Snowflake]
afkTimeout*: Option[int]
icon*: Option[Image]
ownerID*: Option[snowflake]
ownerID*: Option[Snowflake]
splash*: Option[Image]
banner*: Option[Image]
systemChannelID*: Option[snowflake]
rulesChannelID*: Option[snowflake]
publicUpdatesChannelID*: Option[snowflake]
systemChannelID*: Option[Snowflake]
rulesChannelID*: Option[Snowflake]
publicUpdatesChannelID*: Option[Snowflake]
preferredLocale*: Option[string]
proc modifyGuild*(guild: Guild, modify: GuildModify): Future[Guild] {.async.} =
@ -408,49 +408,49 @@ proc modifyGuild*(guild: Guild, modify: GuildModify): Future[Guild] {.async.} =
var modifyPayload = %*{}
if (modify.name.isSome):
if modify.name.isSome:
modifyPayload.add("name", %modify.name.get())
if (modify.region.isSome):
if modify.region.isSome:
modifyPayload.add("region", %modify.region.get())
if (modify.verificationLevel.isSome):
if modify.verificationLevel.isSome:
modifyPayload.add("verification_level", %modify.verificationLevel.get())
if (modify.defaultMessageNotifications.isSome):
if modify.defaultMessageNotifications.isSome:
modifyPayload.add("default_message_notifications", %modify.defaultMessageNotifications.get())
if (modify.explicitContentFilter.isSome):
if modify.explicitContentFilter.isSome:
modifyPayload.add("explicit_content_filter", %modify.explicitContentFilter.get())
if (modify.afkChannelID.isSome):
if modify.afkChannelID.isSome:
modifyPayload.add("afk_channel_id", %modify.afkChannelID.get())
if (modify.afkTimeout.isSome):
if modify.afkTimeout.isSome:
modifyPayload.add("afk_timeout", %modify.afkTimeout.get())
if (modify.icon.isSome):
if modify.icon.isSome:
modifyPayload.add("icon", %modify.icon.get().imageToDataURI())
if (modify.ownerID.isSome):
if modify.ownerID.isSome:
modifyPayload.add("owner_id", %modify.ownerID.get())
if (modify.splash.isSome):
if modify.splash.isSome:
modifyPayload.add("splash", %modify.splash.get().imageToDataURI())
if (modify.banner.isSome):
if modify.banner.isSome:
modifyPayload.add("banner", %modify.banner.get().imageToDataURI())
if (modify.systemChannelID.isSome):
if modify.systemChannelID.isSome:
modifyPayload.add("system_channel_id", %modify.systemChannelID.get())
if (modify.rulesChannelID.isSome):
if modify.rulesChannelID.isSome:
modifyPayload.add("rules_channel_id", %modify.rulesChannelID.get())
if (modify.publicUpdatesChannelID.isSome):
if modify.publicUpdatesChannelID.isSome:
modifyPayload.add("public_updates_channel_id", %modify.publicUpdatesChannelID.get())
if (modify.preferredLocale.isSome):
if modify.preferredLocale.isSome:
modifyPayload.add("preferred_locale", %modify.preferredLocale.get())
return newGuild(sendRequest(endpoint("/guilds/" & $guild.id), HttpPatch,
@ -488,33 +488,33 @@ proc createGuildChannel*(guild: var Guild, create: ChannelFields): Future[Channe
var createPayload = %*{}
# Make sure that the name is supplied since its required for this endpoint.
if (create.name.isSome):
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):
if create.`type`.isSome:
createPayload.add("type", %create.`type`.get())
if (create.position.isSome):
if create.position.isSome:
createPayload.add("position", %create.position.get())
if (create.topic.isSome):
if create.topic.isSome:
createPayload.add("topic", %create.topic.get())
if (create.nsfw.isSome):
if create.nsfw.isSome:
createPayload.add("nsfw", %create.nsfw.get())
if (create.rateLimitPerUser.isSome):
if create.rateLimitPerUser.isSome:
createPayload.add("rate_limit_per_user", %create.rateLimitPerUser.get())
if (create.bitrate.isSome):
if create.bitrate.isSome:
createPayload.add("bitrate", %create.bitrate.get())
if (create.userLimit.isSome):
if create.userLimit.isSome:
createPayload.add("user_limit", %create.userLimit.get())
if (create.permissionOverwrites.isSome):
if create.permissionOverwrites.isSome:
var permOverwrites = parseJson("[]")
for perm in create.permissionOverwrites.get():
permOverwrites.add(perm.permissionsToJson())
@ -536,7 +536,7 @@ proc modifyGuildChannelPositions*(guild: var Guild, channels: seq[Channel]) {.as
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, jsonBody)
proc getGuildMember*(guild: var Guild, memberID: snowflake): GuildMember =
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.
@ -544,7 +544,7 @@ proc getGuildMember*(guild: var Guild, memberID: snowflake): GuildMember =
## If we end up requesting one, it will add it to `guild.members`
for member in guild.members:
if (member.id == memberID):
if member.id == memberID:
return member
result = newGuildMember(sendRequest(endpoint(fmt("/guilds/{guild.id}/members/{memberID}")),
@ -576,7 +576,7 @@ proc getGuildBans*(guild: Guild): seq[GuildBan] =
user: newUser(json["user"])
))
proc getGuildBan*(guild: Guild, userID: snowflake): GuildBan =
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,
@ -590,22 +590,22 @@ proc getGuildBan*(guild: Guild, userID: snowflake): GuildBan =
else:
return nil
proc banGuildMember*(guild: Guild, userID: snowflake, reason: Option[string] = none(string), deleteMessageDays: Option[int] = none(int)) {.async.} =
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):
if reason.isSome:
jsonBody.add("reason", %reason.get())
if (deleteMessageDays.isSome):
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.} =
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)
@ -634,19 +634,19 @@ proc createGuildRole*(guild: Guild, name: Option[string] = none(string), permiss
var jsonBody: JsonNode
if (name.isSome):
if name.isSome:
jsonBody.add("name", %name)
if (permissions.isSome):
if permissions.isSome:
jsonBody.add("permissions", %permissions.get().allowPerms)
if (color.isSome):
if color.isSome:
jsonBody.add("color", %color)
if (hoist.isSome):
if hoist.isSome:
jsonBody.add("hoist", %hoist)
if (mentionable.isSome):
if mentionable.isSome:
jsonBody.add("mentionable", %mentionable)
return newRole(sendRequest(endpoint(fmt("/guilds/{guild.id}/roles")), HttpPost,
@ -665,7 +665,7 @@ proc modifyGuildRolePositions*(guild: var Guild, roles: seq[Role]) {.async.} =
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
guild.id, RateLimitBucketType.guild, jsonBody)
proc getGuildPruneCount*(guild: Guild, days: int = 7, includedRoles: seq[snowflake] = @[]): int =
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.
##
@ -687,7 +687,7 @@ proc getGuildPruneCount*(guild: Guild, days: int = 7, includedRoles: seq[snowfla
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.} =
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.
##
@ -792,15 +792,15 @@ proc modifyGuildIntegration*(guild: Guild, integration: var Integration,
## discard integration.modifyGuildIntegration(enable_emoticons = true)
var modifyPayload = %*{}
if (expireBehavior.isSome):
if expireBehavior.isSome:
modifyPayload.add("expire_behavior", %expireBehavior.get())
integration.expireBehavior = (expireBehavior.get())
if (expireGracePeriod.isSome):
if expireGracePeriod.isSome:
modifyPayload.add("expire_grace_period", %expireGracePeriod.get())
integration.expireGracePeriod = expireGracePeriod.get()
if (enableEmoticons.isSome):
if enableEmoticons.isSome:
modifyPayload.add("enable_emoticons", %enableEmoticons.get())
integration.enableEmoticons = enableEmoticons.get()
@ -827,7 +827,7 @@ proc getGuildWidget*(guild: Guild): GuildWidget =
return GuildWidget(enabled: jsonBody["enabled"].getBool(),
channelID: getIDFromJson(jsonBody{"channel_id"}.getStr()))
proc modifyGuildWidget*(guild: Guild, widget: var GuildWidget, enabled: bool, channelID: snowflake) {.async.} =
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
@ -863,16 +863,16 @@ proc getGuildWidgetImage*(guild: Guild, style: GuildWidgetStyle): string =
## 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):
case style
of GuildWidgetStyle.guildWidgetStyleShield:
result &= "?style=shield"
of (GuildWidgetStyle.guildWidgetStyleBanner1):
of GuildWidgetStyle.guildWidgetStyleBanner1:
result &= "?style=banner1"
of (GuildWidgetStyle.guildWidgetStyleBanner2):
of GuildWidgetStyle.guildWidgetStyleBanner2:
result &= "?style=banner2"
of (GuildWidgetStyle.guildWidgetStyleBanner3):
of GuildWidgetStyle.guildWidgetStyleBanner3:
result &= "?style=banner3"
of (GuildWidgetStyle.guildWidgetStyleBanner4):
of GuildWidgetStyle.guildWidgetStyleBanner4:
result &= "?style=banner4"
proc requestEmojis*(guild: Guild): seq[Emoji] =
@ -887,7 +887,7 @@ proc requestEmojis*(guild: Guild): seq[Emoji] =
result.add(newEmoji(emoji, guild.id))
guild.emojis = result
proc getEmoji*(guild: Guild, emojiID: snowflake): Emoji =
proc getEmoji*(guild: Guild, emojiID: Snowflake): Emoji =
## Returns a guild's emoji with `emojiID`.
## If the emoji isn't found in `guild.emojis` then it will request on
## from the Discord REST API.
@ -898,7 +898,7 @@ proc getEmoji*(guild: Guild, emojiID: snowflake): Emoji =
return newEmoji(sendRequest(endpoint("/guilds/{guild.id}/emojis/{emojiID}"), HttpGet,
defaultHeaders(), guild.id, RateLimitBucketType.guild), guild.id)
proc createEmoji*(guild: Guild, name: string, image: Image, roles: seq[snowflake] = @[]): Future[Emoji] {.async.} =
proc createEmoji*(guild: Guild, name: string, image: Image, roles: seq[Snowflake]): Future[Emoji] {.async.} =
## Create a new emoji for the guild. Requires the `MANAGE_EMOJIS` permission.
var jsonBody = %* {
"name": name,
@ -914,5 +914,5 @@ proc createEmoji*(guild: Guild, name: string, image: Image, roles: seq[snowflake
proc getGuildMemberRoles*(guild: Guild, member: GuildMember): seq[Role] =
## Get the role objects for a member's roles.
for role in guild.roles:
if (member.roles.contains(role.id)):
result.add(role)
if member.roles.contains(role.id):
result.add(role)

View File

@ -9,7 +9,7 @@ proc newImage*(filepath: string): Image =
## Reads from a file that exists at `filepath`. It reads the image data,
## and image extension for later use.
var imageStream = newFileStream(filepath, fmRead)
if (not isNil(imageStream)):
if not isNil(imageStream):
let data = imageStream.readALL()
# Get the file's extension and remove the `.` from the start of it
@ -24,4 +24,4 @@ proc newImage*(filepath: string): Image =
raise newException(IOError, "Failed to open file: " & filepath)
proc imageToDataURI*(image: Image): string =
return fmt("data:image/{image.extension};base64,{image.base64Encoded}")
return fmt("data:image/{image.extension};base64,{image.base64Encoded}")

View File

@ -25,7 +25,7 @@ proc newLog*(flags: int, filePath: string = ""): Log =
var log = Log(flags: flags)
if filePath.len > 0:
log.logFile = newFileStream(filePath, fmWrite)
if (isNil(log.logFile)):
if isNil(log.logFile):
raise newException(IOError, "Failed to open log file: " & filePath)
return log
@ -38,7 +38,7 @@ proc canLog(log: Log, sev: LogSeverity): bool =
elif (log.flags and int(LoggerFlags.loggerFlagDebugSeverity)) == int(LoggerFlags.loggerFlagDebugSeverity):
return true
case (sev)
case sev
of LogSeverity.logSevInfo:
return (log.flags and int(LoggerFlags.loggerFlagInfoSeverity)) == int(LoggerFlags.loggerFlagInfoSeverity);
of LogSeverity.logSevWarn:
@ -49,7 +49,7 @@ proc canLog(log: Log, sev: LogSeverity): bool =
return false;
proc severityToString(sev: LogSeverity): string =
case (sev)
case sev
of LogSeverity.logSevInfo:
return "INFO"
of LogSeverity.logSevWarn:
@ -61,7 +61,7 @@ proc severityToString(sev: LogSeverity): string =
#TODO: Remove colors from file.
template autoLog(log: Log, sev: LogSeverity, args: varargs[untyped]) =
if (log.canLog(sev)):
if log.canLog(sev):
let timeFormated = getTime().format("[HH:mm:ss]")
let sevStr = "[" & severityToString(sev) & "]"
@ -69,7 +69,7 @@ template autoLog(log: Log, sev: LogSeverity, args: varargs[untyped]) =
terminal.styledEcho(logHeader, args)
if (log.logFile != nil):
if log.logFile != nil:
log.logFile.writeLine(logHeader, args)
template debug*(log: Log, args: varargs[untyped]) =
@ -90,6 +90,6 @@ template info*(log: Log, args: varargs[untyped]) =
proc closeLog*(log: Log) =
## Close log file if it was ever open.
if (log.logFile != nil):
if log.logFile != nil:
log.info(fgYellow, "Closing log...")
log.logFile.close()
log.logFile.close()

View File

@ -1,18 +1,18 @@
import discordobject, user, json, role, options, asyncdispatch, nimcordutils, httpcore, strformat, strutils, presence
import discordobject, user, json, options, asyncdispatch, nimcordutils, httpcore, strformat, strutils, presence
type GuildMember* = ref object of DiscordObject
## This type is a guild member.
user*: User ## The user this guild member represents.
nick*: string ## This users guild nickname.
roles*: seq[snowflake] ## Array of roles.
roles*: seq[Snowflake] ## Array of roles.
joinedAt*: string ## When the user joined the guild.
premiumSince*: string ## When the user started boosting the guild.
deaf*: bool ## Whether the user is deafened in voice channels.
mute*: bool ## Whether the user is muted in voice channels.
guildID*: snowflake ## The guild this member is in.
guildID*: Snowflake ## The guild this member is in.
presence*: Presence ## The member's presence.
proc newGuildMember*(json: JsonNode, guild: snowflake): GuildMember {.inline.} =
proc newGuildMember*(json: JsonNode, guild: Snowflake): GuildMember {.inline.} =
## Construct a GuildMember using json.
result = GuildMember(
nick: json{"nick"}.getStr(),
@ -24,7 +24,7 @@ proc newGuildMember*(json: JsonNode, guild: snowflake): GuildMember {.inline.} =
guildID: guild
)
if (json.contains("user")):
if json.contains("user"):
result.user = newUser(json["user"])
# Add roles
@ -33,46 +33,46 @@ proc newGuildMember*(json: JsonNode, guild: snowflake): GuildMember {.inline.} =
type GuildMemberModify* = ref object
nick: Option[string]
roles: Option[seq[snowflake]]
roles: Option[seq[Snowflake]]
mute: Option[bool]
deaf: Option[bool]
channelID: Option[snowflake]
channelID: Option[Snowflake]
proc modifyGuildMember*(member: GuildMember, memberID: snowflake, modify: GuildMemberModify) {.async.} =
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):
if modify.nick.isSome:
modifyPayload.add("nick", %modify.nick.get())
if (modify.roles.isSome):
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):
if modify.mute.isSome:
modifyPayload.add("mute", %modify.mute.get())
if (modify.deaf.isSome):
if modify.deaf.isSome:
modifyPayload.add("deaf", %modify.deaf.get())
if (modify.channelID.isSome):
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.} =
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.} =
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)
HttpDelete, defaultHeaders(), member.guildID, RateLimitBucketType.guild)

View File

@ -1,4 +1,4 @@
import json, discordobject, nimcordutils, user, member, httpcore, asyncdispatch, emoji, options, embed, role, emoji
import json, discordobject, nimcordutils, user, member, httpcore, asyncdispatch, emoji, options, embed, emoji
type
MessageType* = enum
@ -35,9 +35,9 @@ type
name*: string
MessageReference* = ref object
messageID*: snowflake
channelID*: snowflake
guildID*: snowflake
messageID*: Snowflake
channelID*: Snowflake
guildID*: Snowflake
MessageFlags* = enum
msgFlagCrossposted = 0,
@ -53,8 +53,8 @@ type
ChannelMention* = ref object
## Represents a channel mention inside of a message.
channelID*: snowflake
guildID*: snowflake
channelID*: Snowflake
guildID*: Snowflake
channelType*: int
name*: string
@ -68,8 +68,8 @@ type
width*: int
Message* = ref object of DiscordObject
channelID*: snowflake
guildID*: snowflake
channelID*: Snowflake
guildID*: Snowflake
author*: User
member*: GuildMember
content*: string
@ -78,13 +78,13 @@ type
tts*: bool
mentionEveryone*: bool
mentions*: seq[User]
mentionRoles*: seq[snowflake]
mentionRoles*: seq[Snowflake]
mentionChannels*: seq[ChannelMention]
attachments*: seq[MessageAttachment]
embeds*: seq[Embed]
reactions*: seq[Reaction]
pinned*: bool
webhookID*: snowflake
webhookID*: Snowflake
`type`*: MessageType
activity*: MessageActivity
application*: MessageApplication
@ -107,19 +107,19 @@ proc newMessage*(messageJson: JsonNode): Message =
flags: messageJson{"flags"}.getInt()
)
if (messageJson.contains("author")):
if messageJson.contains("author"):
msg.author = newUser(messageJson["author"])
if (messageJson.contains("member")):
if messageJson.contains("member"):
msg.member = newGuildMember(messageJson["member"], msg.guildID)
if (messageJson.contains("mentions")):
if messageJson.contains("mentions"):
for userJson in messageJson["mentions"]:
msg.mentions.add(newUser(userJson))
for role in messageJson["mention_roles"]:
msg.mentionRoles.add(getIDFromJson(role.getStr()))
if (messageJson.contains("mention_channels")):
if messageJson.contains("mention_channels"):
for channel in messageJson["mention_channels"]:
msg.mentionChannels.add(ChannelMention(
channelID: getIDFromJson(channel["id"].getStr()),
@ -142,7 +142,7 @@ proc newMessage*(messageJson: JsonNode): Message =
for embed in messageJson["embeds"]:
msg.embeds.add(Embed(embedJson: embed))
if (messageJson.contains("reactions")):
if messageJson.contains("reactions"):
for reaction in messageJson["reactions"]:
msg.reactions.add(Reaction(
count: uint(reaction["count"].getInt()),
@ -150,10 +150,10 @@ proc newMessage*(messageJson: JsonNode): Message =
emoji: newEmoji(reaction["emoji"], msg.guildID)
))
if (messageJson.contains("activity")):
if messageJson.contains("activity"):
msg.activity = MessageActivity(`type`: MessageActivityType(messageJson["activity"]["type"].getInt()),
partyID: messageJson["activity"]["party_id"].getStr())
if (messageJson.contains("application")):
if messageJson.contains("application"):
msg.application = MessageApplication(
id: getIDFromJson(messageJson["application"]["id"].getStr()),
coverImage: messageJson["application"]{"cover_image"}.getStr(),
@ -161,7 +161,7 @@ proc newMessage*(messageJson: JsonNode): Message =
icon: messageJson["application"]{"icon"}.getStr(),
name: messageJson["application"]["name"].getStr()
)
if (messageJson.contains("message_reference")):
if messageJson.contains("message_reference"):
msg.messageReference = MessageReference(
messageID: getIDFromJson(messageJson["message_reference"]{"message_id"}.getStr()),
channelID: getIDFromJson(messageJson["message_reference"]["channel_id"].getStr()),
@ -212,8 +212,8 @@ type ReactantsGetRequest* = object
## Use this type to get a messages's reactants by setting
## some of the fields.
## You can only set one of `before` and `after`.
before*: Option[snowflake]
after*: Option[snowflake]
before*: Option[Snowflake]
after*: Option[Snowflake]
limit*: Option[int]
proc getReactants*(message: Message, emoji: Emoji, request: ReactantsGetRequest): seq[User] =
@ -224,17 +224,17 @@ proc getReactants*(message: Message, emoji: Emoji, request: ReactantsGetRequest)
# Raise some exceptions to make sure the user doesn't
# try to set more than one of these fields
if (request.before.isSome):
if request.before.isSome:
url = url & "before=" & $request.before.get()
if (request.after.isSome):
if (request.before.isSome):
if request.after.isSome:
if request.before.isSome:
raise newException(Defect, "You cannot get before and after a message! Choose one...")
url = url & "after=" & $request.after.get()
if (request.limit.isSome):
if request.limit.isSome:
# Add the `&` for the url if something else is set.
if (request.before.isSome or request.after.isSome):
if request.before.isSome or request.after.isSome:
url = url & "&"
url = url & "limit=" & $request.limit.get()
@ -276,7 +276,7 @@ proc editMessage*(message: Message, content: string, embed: Embed = nil): Future
## Edit a previously sent message.
var jsonBody = %*{"content": content}
if (not embed.isNil()):
if embed != nil:
jsonBody.add("embed", embed.embedJson)
return newMessage(sendRequest(endpoint("/channels/" & $message.channelID & "/messages/" & $message.id),
@ -291,4 +291,4 @@ proc deleteMessage*(message: Message) {.async.} =
## See also:
## * `deleteMessage<#deleteMessage,Message>`_
discard sendRequest(endpoint("/channels/" & $message.channelID & "/messages/" & $message.id),
HttpDelete, defaultHeaders(), message.channelID, RateLimitBucketType.channel)
HttpDelete, defaultHeaders(), message.channelID, RateLimitBucketType.channel)

View File

@ -1,5 +1,5 @@
import parseutils, json, httpClient, strformat, tables, times, asyncdispatch
from discordobject import snowflake
from discordobject import Snowflake
proc getIDFromJson*(str: string): uint64 =
var num: uint64
@ -17,10 +17,11 @@ proc endpoint*(url: string): string =
var globalToken*: string
proc defaultHeaders*(added: HttpHeaders = newHttpHeaders()): HttpHeaders =
added.add("Authorization", fmt("Bot {globalToken}"))
# added.add("Authorization", fmt("Bot {globalToken}"))
added.add("Authorization", fmt("{globalToken}"))
added.add("User-Agent", "NimCord (https://github.com/SeanOMik/nimcord, v0.0.0)")
added.add("X-RateLimit-Precision", "millisecond")
return added;
return added
type
RateLimitBucketType* = enum
@ -37,16 +38,16 @@ proc newRateLimit(lmt: int = 500, remLmnt: int = 500, ratelmtReset: float = 0):
return RateLimit(limit: lmt, remainingLimit: remLmnt, ratelimitReset: ratelmtReset)
# Rate limit buckets
let channelRatelimitBucket = newTable[snowflake, RateLimit]()
let guildRatelimitBucket = newTable[snowflake, RateLimit]()
let webhookRatelimitBucket = newTable[snowflake, RateLimit]()
let channelRatelimitBucket = newTable[Snowflake, RateLimit]()
let guildRatelimitBucket = newTable[Snowflake, RateLimit]()
let webhookRatelimitBucket = newTable[Snowflake, RateLimit]()
var globalRateLimit: RateLimit = newRateLimit()
proc handleRateLimits*(headers: HttpHeaders, objectID: snowflake, bucketType: RateLimitBucketType) =
proc handleRateLimits*(headers: HttpHeaders, objectID: Snowflake, bucketType: RateLimitBucketType) =
var obj: RateLimit
if (headers.hasKey("x-ratelimit-global")):
if headers.hasKey("x-ratelimit-global"):
obj = globalRateLimit
elif (headers.hasKey("x-ratelimit-limit")):
elif headers.hasKey("x-ratelimit-limit"):
case bucketType:
of RateLimitBucketType.channel:
obj = channelRatelimitBucket[objectID]
@ -68,33 +69,33 @@ proc handleRateLimits*(headers: HttpHeaders, objectID: snowflake, bucketType: Ra
discard parseFloat(headers["x-ratelimit-reset"], obj.ratelimitReset)
proc handleResponse*(response: Response, objectID: snowflake, bucketType: RateLimitBucketType): JsonNode =
proc handleResponse*(response: Response, objectID: Snowflake, bucketType: RateLimitBucketType): JsonNode =
echo fmt("Received requested payload: {response.body}")
handleRateLimits(response.headers, objectID, bucketType)
return parseJson(response.body())
proc waitForRateLimits*(objectID: snowflake, bucketType: RateLimitBucketType) =
proc waitForRateLimits*(objectID: Snowflake, bucketType: RateLimitBucketType) =
var rlmt: RateLimit
if (globalRateLimit.remainingLimit == 0):
if globalRateLimit.remainingLimit == 0:
rlmt = globalRateLimit
else:
case bucketType:
of RateLimitBucketType.channel:
if (channelRatelimitBucket.hasKey(objectID)):
if channelRatelimitBucket.hasKey(objectID):
rlmt = channelRatelimitBucket[objectID]
else:
channelRatelimitBucket.add(objectID, newRateLimit())
rlmt = channelRatelimitBucket[objectID]
of RateLimitBucketType.guild:
if (guildRatelimitBucket.hasKey(objectID)):
if guildRatelimitBucket.hasKey(objectID):
rlmt = guildRatelimitBucket[objectID]
else:
guildRatelimitBucket.add(objectID, newRateLimit())
rlmt = guildRatelimitBucket[objectID]
of RateLimitBucketType.webhook:
if (webhookRatelimitBucket.hasKey(objectID)):
if webhookRatelimitBucket.hasKey(objectID):
rlmt = webhookRatelimitBucket[objectID]
else:
webhookRatelimitBucket.add(objectID, newRateLimit())
@ -102,21 +103,21 @@ proc waitForRateLimits*(objectID: snowflake, bucketType: RateLimitBucketType) =
of RateLimitBucketType.global:
rlmt = globalRateLimit
if (rlmt != nil and rlmt.remainingLimit == 0):
if rlmt != nil and rlmt.remainingLimit == 0:
let millisecondTime: float = rlmt.ratelimitReset * 1000 - epochTime() * 1000
if (millisecondTime > 0):
if millisecondTime > 0:
echo fmt("Rate limit wait time: {millisecondTime} miliseconds")
waitFor sleepAsync(millisecondTime)
proc sendRequest*(endpoint: string, httpMethod: HttpMethod, headers: HttpHeaders, objectID: snowflake = 0,
proc sendRequest*(endpoint: string, httpMethod: HttpMethod, headers: HttpHeaders, objectID: Snowflake = 0,
bucketType: RateLimitBucketType = global, jsonBody: JsonNode = nil): JsonNode =
var client = newHttpClient()
# Add headers
client.headers = headers
var strPayload: string
if (jsonBody == nil):
if jsonBody == nil:
strPayload = ""
else:
strPayload = $jsonBody
@ -124,4 +125,4 @@ proc sendRequest*(endpoint: string, httpMethod: HttpMethod, headers: HttpHeaders
waitForRateLimits(objectID, bucketType)
let response = client.request(endpoint, httpMethod, strPayload)
return handleResponse(response, objectId, bucketType)
return handleResponse(response, objectId, bucketType)

View File

@ -39,12 +39,12 @@ type
Permissions* = ref object
## This type referes to a user's permissions given by the role or per user.
roleUserID*: snowflake
roleUserID*: Snowflake
allowPerms*: uint
denyPerms*: uint
permissionType*: PermissionType
proc newPermissions*(id: snowflake, `type`: PermissionType, byteSet: uint): Permissions =
proc newPermissions*(id: Snowflake, `type`: PermissionType, byteSet: uint): Permissions =
## Create a new `Permissions` using an id, type, and byte set.
result = Permissions(roleUserID: id, permissionType: `type`, allowPerms: byteSet)
@ -56,7 +56,7 @@ proc newPermissions*(json: JsonNode): Permissions =
denyPerms: uint(json["deny"].getInt())
)
if (json["type"].getStr() == "role"):
if json["type"].getStr() == "role":
result.permissionType = PermissionType.permTypeRole
else:
result.permissionType = PermissionType.permTypeMember
@ -71,7 +71,7 @@ proc addAllowPermission*(perms: Permissions, perm: Permission): Future[Permissio
## If it finds the permission in denyPerms, it will remove it from that also.
# Check if the permission is in deny, and remove it.
if ((perms.denyPerms and uint(perm)) == uint(perm)):
if (perms.denyPerms and uint(perm)) == uint(perm):
perms.denyPerms = perms.denyPerms and (not uint(perm))
perms.allowPerms = perms.allowPerms or uint(perm)
@ -81,7 +81,7 @@ proc addDenyPermission*(perms: Permissions, perm: Permission): Future[Permission
## If it finds the permission in allowPerms, it will remove it from that also.
# Check if the permission is in allowed, and remove it.
if ((perms.allowPerms and uint(perm)) == uint(perm)):
if (perms.allowPerms and uint(perm)) == uint(perm):
perms.allowPerms = perms.allowPerms and (not uint(perm))
perms.denyPerms = perms.denyPerms or uint(perm)
@ -94,7 +94,7 @@ proc permissionsToJson*(perms: Permissions): JsonNode =
"deny": perms.denyPerms
}
if (perms.permissionType == PermissionType.permTypeMember):
if perms.permissionType == PermissionType.permTypeMember:
json.add("type", %"member")
else:
json.add("type", %"role")
json.add("type", %"role")

View File

@ -48,7 +48,7 @@ type
url*: string
createdAt*: uint
timestamps*: seq[ActivityTimestamp]
applicationID*: snowflake
applicationID*: Snowflake
details*: string
state*: string
emoji*: Emoji
@ -64,7 +64,7 @@ type
activities*: seq[Activity]
afk*: bool
proc newActivity*(json: JsonNode, guildID: snowflake): Activity =
proc newActivity*(json: JsonNode, guildID: Snowflake): Activity =
## Parse a new activity from json.
var act = Activity(
name: json["name"].getStr(),
@ -78,44 +78,44 @@ proc newActivity*(json: JsonNode, guildID: snowflake): Activity =
flags: uint(json{"flags"}.getInt()),
)
if (json.contains("timestamps")):
if json.contains("timestamps"):
var time = ActivityTimestamp()
if (json["timestamps"].contains("start")):
if json["timestamps"].contains("start"):
time.startTime = uint(json["timestamps"]{"start"}.getInt())
if (json["timestamps"].contains("end")):
if json["timestamps"].contains("end"):
time.endTime = uint(json["timestamps"]{"end"}.getInt())
act.timestamps.add(time)
if (json.contains("emoji")):
if json.contains("emoji"):
act.emoji = newEmoji(json["emoji"], guildID)
if (json.contains("party")):
if json.contains("party"):
var party = ActivityParty()
if (json["party"].contains("id")):
if json["party"].contains("id"):
party.id = json["party"]["id"].getStr()
if (json["party"].contains("size")):
if json["party"].contains("size"):
party.currentSize = uint(json["party"]["size"].elems[0].getInt())
party.maxSize = uint(json["party"]["size"].elems[1].getInt())
if (json.contains("assets")):
if json.contains("assets"):
var assets = ActivityAssets()
if (json["assets"].contains("large_image")):
if json["assets"].contains("large_image"):
assets.largeImg = json["assets"]["large_image"].getStr()
if (json["assets"].contains("large_text")):
if json["assets"].contains("large_text"):
assets.largeText = json["assets"]["large_text"].getStr()
if (json["assets"].contains("small_image")):
if json["assets"].contains("small_image"):
assets.smallImg = json["assets"]["small_image"].getStr()
if (json["assets"].contains("small_text")):
if json["assets"].contains("small_text"):
assets.smallText = json["assets"]["small_text"].getStr()
if (json.contains("secrets")):
if json.contains("secrets"):
var secrets = ActivitySecrets()
if (json["secrets"].contains("join")):
if json["secrets"].contains("join"):
secrets.join = json["secrets"]["join"].getStr()
if (json["secrets"].contains("spectate")):
if json["secrets"].contains("spectate"):
secrets.spectate = json["secrets"]["spectate"].getStr()
if (json["secrets"].contains("match")):
if json["secrets"].contains("match"):
secrets.match = json["secrets"]["match"].getStr()
proc newPresence*(json: JsonNode): Presence =
@ -124,7 +124,7 @@ proc newPresence*(json: JsonNode): Presence =
status: json["status"].getStr()
)
if (json.contains("game") and json["game"].getFields().len > 0):
if json.contains("game") and json["game"].getFields().len > 0:
let guildID = if json.contains("guild_id"): getIDFromJson(json["guild_id"].getStr()) else: 0
result.game = newActivity(json["game"], guildID)
@ -154,4 +154,4 @@ proc presenceToJson*(presence: Presence): JsonNode =
"name": presence.game.name,
"type": ord(presence.game.`type`)
}
}
}

View File

@ -8,9 +8,9 @@ type Role* = ref object of DiscordObject
permissions*: Permissions
managed*: bool
mentionable*: bool
guildID*: snowflake
guildID*: Snowflake
proc newRole*(json: JsonNode, guild: snowflake): Role =
proc newRole*(json: JsonNode, guild: Snowflake): Role =
## Parses role from json.
result = Role(
id: getIDFromJson(json["id"].getStr()),
@ -38,19 +38,19 @@ proc modifyGuildRole*(role: var Role, name: Option[string] = none(string), permi
var jsonBody: JsonNode
if (name.isSome):
if name.isSome:
jsonBody.add("name", %name)
if (permissions.isSome):
if permissions.isSome:
jsonBody.add("permissions", %permissions.get().allowPerms)
if (color.isSome):
if color.isSome:
jsonBody.add("color", %color)
if (hoist.isSome):
if hoist.isSome:
jsonBody.add("hoist", %hoist)
if (mentionable.isSome):
if mentionable.isSome:
jsonBody.add("mentionable", %mentionable)
result = newRole(sendRequest(endpoint(fmt("/guilds/{role.guildID}/roles/{role.id}")), HttpPatch,
@ -61,4 +61,4 @@ proc modifyGuildRole*(role: var Role, name: Option[string] = none(string), permi
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)
defaultHeaders(), role.guildID, RateLimitBucketType.guild)