Make a bunch of image url getters and memory optimize the image hashes

This commit is contained in:
SeanOMik 2020-08-13 19:39:38 -05:00
parent fadf533066
commit 0764147bed
No known key found for this signature in database
GPG Key ID: FA4D55AC05268A88
5 changed files with 220 additions and 27 deletions

View File

@ -22,7 +22,7 @@ proc getIdentifyPacket(shard: Shard): JsonNode
proc handleGatewayDisconnect(shard: Shard, error: string) {.async.} proc handleGatewayDisconnect(shard: Shard, error: string) {.async.}
proc handleHeartbeat(shard: Shard) {.async.} proc handleHeartbeat(shard: Shard) {.async.}
proc handleWebsocketPacket(shard: Shard) {.async.} proc handleWebsocketPacket(shard: Shard) {.async.}
proc newDiscordClient*(tkn: string): DiscordClient proc newDiscordClient*(tkn: string, commandPrefix: string): DiscordClient
proc newShard(shardID: int, client: DiscordClient): Shard proc newShard(shardID: int, client: DiscordClient): Shard
proc reconnectShard(shard: Shard) {.async.} proc reconnectShard(shard: Shard) {.async.}
proc sendGatewayRequest*(shard: Shard, request: JsonNode, msg: string = "") {.async.} proc sendGatewayRequest*(shard: Shard, request: JsonNode, msg: string = "") {.async.}

View File

@ -96,3 +96,21 @@ proc deleteEmoji*(emoji: Emoji) {.async.} =
## Delete the given emoji. Requires the `MANAGE_EMOJIS` permission. ## Delete the given emoji. Requires the `MANAGE_EMOJIS` permission.
discard sendRequest(endpoint(fmt("/guilds/{emoji.guildID}/emojis/{emoji.id}")), HttpDelete, discard sendRequest(endpoint(fmt("/guilds/{emoji.guildID}/emojis/{emoji.id}")), HttpDelete,
defaultHeaders(), emoji.guildID, RateLimitBucketType.guild) defaultHeaders(), emoji.guildID, RateLimitBucketType.guild)
proc getEmojiURL*(emoji: Emoji, imageType: ImageType = ImageType.imgTypeAuto): string =
## Get the URL for the emoji's.
result = "https://cdn.discordapp.com/emojis/" & $emoji.id
# Choose the type of image automaticly.
var tmp = imageType
if emoji.animated:
tmp = ImageType.imgTypeGif
else:
tmp = ImageType.imgTypePng
case tmp:
of ImageType.imgTypeGif:
result &= ".gif"
discard
else: # The only other possible image type is png
result &= ".png"

View File

@ -1,6 +1,6 @@
import json, discordobject, channel, member, options, nimcordutils, emoji import json, discordobject, channel, member, options, nimcordutils, emoji
import role, permission, httpcore, strformat, image, asyncdispatch, user import role, permission, httpcore, strformat, image, asyncdispatch, user
import permission, presence, tables import permission, presence, tables, strutils
type type
VerificationLevel* = enum VerificationLevel* = enum
@ -68,9 +68,10 @@ type
Guild* = ref object of DiscordObject Guild* = ref object of DiscordObject
## Discord Guild object ## Discord Guild object
name*: string ## The name of the current guild name*: string ## The name of the current guild
icon*: string ## The hash of the current guild's icon iconRaw: array[2, uint64] ## The split hash for the 128bit hexadeximal icon.
splash*: string ## The hash of the current guild's splash isIconGif: bool ## Wether the avatar is a gif.
discoverySplash*: string splashRaw: array[2, uint64] ## The split hash for the 128bit hexadeximal splash.
discoverySplashRaw: array[2, uint64] ## The split hash for the 128bit hexadeximal discovery splash.
owner*: bool ## Whether or not the current user is the owner of the current guild owner*: bool ## Whether or not the current user is the owner of the current guild
ownerID: Snowflake ## The snowflake id of the current guild's owner ownerID: Snowflake ## The snowflake id of the current guild's owner
permissions*: Permissions permissions*: Permissions
@ -102,7 +103,7 @@ type
maxMembers*: int ## The maximum amount of members in the current guild? maxMembers*: int ## The maximum amount of members in the current guild?
vanityUrlCode*: string ## The vanity invite for the current guild (ex: https://discord.gg/discord-api) vanityUrlCode*: string ## The vanity invite for the current guild (ex: https://discord.gg/discord-api)
description*: string description*: string
banner*: string ## The hash code of the current guild bannerRaw: array[2, uint64] ## The split hash for the 128bit hexadeximal banner.
premiumTier*: PremiumTier premiumTier*: PremiumTier
premiumSubscriptionCount*: int premiumSubscriptionCount*: int
preferredLocale*: string preferredLocale*: string
@ -175,9 +176,8 @@ proc newGuild*(json: JsonNode): Guild {.inline.} =
var g = Guild( var g = Guild(
id: getIDFromJson(json["id"].getStr()), id: getIDFromJson(json["id"].getStr()),
name: json["name"].getStr(), name: json["name"].getStr(),
icon: json["icon"].getStr(), splashRaw: splitAvatarHash(json["splash"].getStr()), # No need to remove prefixed "a_", can't be animated.
splash: json["splash"].getStr(), discoverySplashRaw: splitAvatarHash(json["discovery_splash"].getStr()), # No need to remove prefixed "a_", can't be animated.
discoverySplash: json["discovery_splash"].getStr(),
ownerID: getIDFromJson(json["owner_id"].getStr()), ownerID: getIDFromJson(json["owner_id"].getStr()),
region: json["region"].getStr(), region: json["region"].getStr(),
afkChannelID: getIDFromJson(json["afk_channel_id"].getStr()), afkChannelID: getIDFromJson(json["afk_channel_id"].getStr()),
@ -192,13 +192,23 @@ proc newGuild*(json: JsonNode): Guild {.inline.} =
rulesChannelID: getIDFromJson(json["rules_channel_id"].getStr()), rulesChannelID: getIDFromJson(json["rules_channel_id"].getStr()),
vanityUrlCode: json["vanity_url_code"].getStr(), vanityUrlCode: json["vanity_url_code"].getStr(),
description: json["description"].getStr(), description: json["description"].getStr(),
banner: json["banner"].getStr(), bannerRaw: splitAvatarHash(json["banner"].getStr()), # No need to remove prefixed "a_", can't be animated.
premiumTier: PremiumTier(json["premium_tier"].getInt()), premiumTier: PremiumTier(json["premium_tier"].getInt()),
preferredLocale: json["preferred_locale"].getStr(), preferredLocale: json["preferred_locale"].getStr(),
publicUpdatesChannelID: getIDFromJson(json["public_updates_channel_id"].getStr()) publicUpdatesChannelID: getIDFromJson(json["public_updates_channel_id"].getStr())
) )
# Parse all non guaranteed fields # Parse all non guaranteed fields
if json.contains("icon"):
let iconStr = json["icon"].getStr()
# If the icon is animated we need to remove the prefixed "a_"
if iconStr.startsWith("a_"):
g.isIconGif = true
g.iconRaw = splitAvatarHash(iconStr.substr(2))
else:
g.isIconGif = false
g.iconRaw = splitAvatarHash(iconStr)
if json.contains("owner"): if json.contains("owner"):
g.owner = json["owner"].getBool() g.owner = json["owner"].getBool()
if json.contains("permissions"): if json.contains("permissions"):
@ -916,3 +926,96 @@ proc getGuildMemberRoles*(guild: Guild, member: GuildMember): seq[Role] =
for role in guild.roles: for role in guild.roles:
if member.roles.contains(role.id): if member.roles.contains(role.id):
result.add(role) result.add(role)
proc getGuildIconURL*(guild: Guild, imageType: ImageType = ImageType.imgTypeAuto): string =
## Get the URL for the guild's icon.
result = "https://cdn.discordapp.com/icons/" & $guild.id & "/" & $combineAvatarHash(guild.iconRaw)
# If we're finding the image type automaticly, then we need to
# check if the avatar is a gif.
var tmp = imageType
if (imageType == ImageType.imgTypeAuto):
if guild.isIconGif:
tmp = ImageType.imgTypeGif
else:
tmp = ImageType.imgTypePng
case tmp:
of ImageType.imgTypeGif:
result &= ".gif"
discard
of ImageType.imgTypeJpeg:
result &= ".jpeg"
discard
of ImageType.imgTypePng:
result &= ".png"
discard
of ImageType.imgTypeWebp:
result &= ".webp"
discard
of ImageType.imgTypeAuto:
result &= ".png" # Just incase
discard
proc getGuildSplashURL*(guild: Guild, imageType: ImageType = ImageType.imgTypePng): string =
## Get the URL for the guild's splash.
result = "https://cdn.discordapp.com/splashes/" & $guild.id & "/" & $combineAvatarHash(guild.splashRaw)
case imageType:
of ImageType.imgTypeGif:
result &= ".png" # The guild's splash can't be a gif.
discard
of ImageType.imgTypeJpeg:
result &= ".jpeg"
discard
of ImageType.imgTypePng:
result &= ".png"
discard
of ImageType.imgTypeWebp:
result &= ".webp"
discard
of ImageType.imgTypeAuto:
result &= ".png"
discard
proc getGuildDiscoverySplashURL*(guild: Guild, imageType: ImageType = ImageType.imgTypePng): string =
## Get the URL for the guild's discovery splash.
result = "https://cdn.discordapp.com/discovery-splashes/" & $guild.id & "/" & $combineAvatarHash(guild.discoverySplashRaw)
case imageType:
of ImageType.imgTypeGif:
result &= ".png" # The guild's discovery splash can't be a gif.
discard
of ImageType.imgTypeJpeg:
result &= ".jpeg"
discard
of ImageType.imgTypePng:
result &= ".png"
discard
of ImageType.imgTypeWebp:
result &= ".webp"
discard
of ImageType.imgTypeAuto:
result &= ".png" # Just incase
discard
proc getGuildBannerURL*(guild: Guild, imageType: ImageType = ImageType.imgTypePng): string =
## Get the URL for the guild's banner.
result = "https://cdn.discordapp.com/banners/" & $guild.id & "/" & $combineAvatarHash(guild.bannerRaw)
case imageType:
of ImageType.imgTypeGif:
result &= ".png" # The guild's banner can't be a gif.
discard
of ImageType.imgTypeJpeg:
result &= ".jpeg"
discard
of ImageType.imgTypePng:
result &= ".png"
discard
of ImageType.imgTypeWebp:
result &= ".webp"
discard
of ImageType.imgTypeAuto:
result &= ".png" # Just incase
discard

View File

@ -1,6 +1,13 @@
import parseutils, json, httpClient, strformat, tables, times, asyncdispatch import parseutils, json, httpClient, strformat, tables, times, asyncdispatch, strutils
from discordobject import Snowflake from discordobject import Snowflake
type ImageType* = enum
imgTypeAuto = 0,
imgTypeWebp = 1,
imgTypePng = 2,
imgTypeJpeg = 3,
imgTypeGif = 4
proc getIDFromJson*(str: string): uint64 = proc getIDFromJson*(str: string): uint64 =
var num: uint64 var num: uint64
discard parseBiggestUInt(str, num) discard parseBiggestUInt(str, num)
@ -17,12 +24,22 @@ proc endpoint*(url: string): string =
var globalToken*: string var globalToken*: string
proc defaultHeaders*(added: HttpHeaders = newHttpHeaders()): HttpHeaders = 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("User-Agent", "NimCord (https://github.com/SeanOMik/nimcord, v0.0.0)")
added.add("X-RateLimit-Precision", "millisecond") added.add("X-RateLimit-Precision", "millisecond")
return added return added
proc splitAvatarHash*(hash: string): array[2, uint64] =
var first: uint64
discard parseBiggestUInt(hash.substr(0, 16), first)
var second: uint64
discard parseBiggestUInt(hash.substr(0, 16), first)
return [first, second]
proc combineAvatarHash*(hash: array[2, uint64]): string =
return (BiggestInt hash[0]).toHex(16) & (BiggestInt hash[1]).toHex(16)
type type
RateLimitBucketType* = enum RateLimitBucketType* = enum
channel, channel,
@ -125,4 +142,4 @@ proc sendRequest*(endpoint: string, httpMethod: HttpMethod, headers: HttpHeaders
waitForRateLimits(objectID, bucketType) waitForRateLimits(objectID, bucketType)
let response = client.request(endpoint, httpMethod, strPayload) let response = client.request(endpoint, httpMethod, strPayload)
return handleResponse(response, objectId, bucketType) return handleResponse(response, objectId, bucketType)

View File

@ -1,4 +1,4 @@
import json, discordobject, nimcordutils import json, discordobject, nimcordutils, strutils
type type
NitroSubscription* = enum NitroSubscription* = enum
@ -7,41 +7,52 @@ type
nitro = 2 nitro = 2
User* = ref object of DiscordObject User* = ref object of DiscordObject
## This type is a discord user. ## This type is any discord user.
username*: string ## The user's username, not unique across the platform. username*: string ## The user's username, not unique across the platform.
discriminator*: cushort ## The user's 4-digit discord-tag. discriminator*: cushort ## The user's 4-digit discord-tag.
avatar*: string ## The user's avatar hash.
bot*: bool ## Whether the user belongs to an OAuth2 application. bot*: bool ## Whether the user belongs to an OAuth2 application.
system*: bool ## Whether the user is an Official Discord System user (part of the urgent message system). system*: bool ## Whether the user is an Official Discord System user (part of the urgent message system).
flags*: int ## The flags on a user's account. publicFlags*: int ## The public [flags](https://discord.com/developers/docs/resources/user#user-object-user-flags) on a user's account. (User Badges)
premiumType*: NitroSubscription ## The type of Nitro subscription on a user's account. avatarRaw: array[2, uint64] ## The split hash for the 128bit hexadeximal avatar.
publicFlags*: int ## The public flags on a user's account. isAvatarGif: bool ## Wether the avatar is a gif.
ClientUser* = ref object of User ClientUser* = ref object of User
## This type is the clients discord user.
mfaEnabled*: bool ## Whether the user has two factor authentication enabled on their account. mfaEnabled*: bool ## Whether the user has two factor authentication enabled on their account.
locale*: string ## The user's chosen language option. locale*: string ## The user's chosen language option.
verified*: bool ## Whether or not the current user has a verified email. verified*: bool ## Whether or not the current user has a verified email.
email*: string ## The current user's email email*: string ## The current user's email
premiumType*: NitroSubscription ## The type of Nitro subscription on a user's account.
flags*: int ## The [flags](https://discord.com/developers/docs/resources/user#user-object-user-flags) on a user's account.
proc newUser*(user: JsonNode): User {.inline.} = proc newUser*(user: JsonNode): User {.inline.} =
return User( result = User(
id: getIDFromJson(user["id"].getStr()), id: getIDFromJson(user["id"].getStr()),
username: user["username"].getStr(), username: user["username"].getStr(),
discriminator: cushort(parseIntEasy(user["discriminator"].getStr())), discriminator: cushort(parseIntEasy(user["discriminator"].getStr())),
avatar: user["avatar"].getStr(),
bot: user{"bot"}.getBool(), bot: user{"bot"}.getBool(),
system: user{"system"}.getBool(), system: user{"system"}.getBool(),
flags: user{"flags"}.getInt(),
premiumType: NitroSubscription(user{"premium_type"}.getInt()),
publicFlags: user{"public_flags"}.getInt() publicFlags: user{"public_flags"}.getInt()
) )
if user.contains("avatar"):
let avatarStr = user["avatar"].getStr()
# If the avatar is animated we need to remove the prefixed "a_"
if avatarStr.startsWith("a_"):
result.isAvatarGif = true
result.avatarRaw = splitAvatarHash(avatarStr.substr(2))
else:
result.isAvatarGif = false
result.avatarRaw = splitAvatarHash(avatarStr)
proc newClientUser*(clientUser: JsonNode): ClientUser {.inline.} = proc newClientUser*(clientUser: JsonNode): ClientUser {.inline.} =
return ClientUser( result = ClientUser(
id: getIDFromJson(clientUser["id"].getStr()), id: getIDFromJson(clientUser["id"].getStr()),
username: clientUser["username"].getStr(), username: clientUser["username"].getStr(),
discriminator: cushort(parseIntEasy(clientUser["discriminator"].getStr())), discriminator: cushort(parseIntEasy(clientUser["discriminator"].getStr())),
avatar: clientUser["avatar"].getStr(),
bot: clientUser{"bot"}.getBool(), bot: clientUser{"bot"}.getBool(),
system: clientUser{"system"}.getBool(), system: clientUser{"system"}.getBool(),
mfaEnabled: clientUser{"mfa_enabled"}.getBool(), mfaEnabled: clientUser{"mfa_enabled"}.getBool(),
@ -51,4 +62,48 @@ proc newClientUser*(clientUser: JsonNode): ClientUser {.inline.} =
flags: clientUser{"flags"}.getInt(), flags: clientUser{"flags"}.getInt(),
premiumType: NitroSubscription(clientUser{"premium_type"}.getInt()), premiumType: NitroSubscription(clientUser{"premium_type"}.getInt()),
publicFlags: clientUser{"public_flags"}.getInt() publicFlags: clientUser{"public_flags"}.getInt()
) )
if clientUser.contains("avatar"):
let avatarStr = clientUser["avatar"].getStr()
# If the avatar is animated we need to remove the prefixed "a_"
if avatarStr.startsWith("a_"):
result.isAvatarGif = true
result.avatarRaw = splitAvatarHash(avatarStr.substr(2))
else:
result.isAvatarGif = false
result.avatarRaw = splitAvatarHash(avatarStr)
proc getUserAvatarURL*(user: User, imageType: ImageType = ImageType.imgTypeAuto): string =
# If the user doesn't have an avatar, then return a default avatar url.
if user.avatarRaw.len == 0:
return "https://cdn.discordapp.com/embed/avatars/" & $(user.discriminator mod 5) & ".png"
result = "https://cdn.discordapp.com/avatars/" & $user.id & "/" & $combineAvatarHash(user.avatarRaw)
# If we're finding the image type automaticly, then we need to
# check if the avatar is a gif.
var tmp = imageType
if (imageType == ImageType.imgTypeAuto):
if user.isAvatarGif:
tmp = ImageType.imgTypeGif
else:
tmp = ImageType.imgTypePng
case tmp:
of ImageType.imgTypeGif:
result &= ".gif"
discard
of ImageType.imgTypeJpeg:
result &= ".jpeg"
discard
of ImageType.imgTypePng:
result &= ".png"
discard
of ImageType.imgTypeWebp:
result &= ".webp"
discard
of ImageType.imgTypeAuto:
result &= ".png" # Just incase
discard