Skip to content

Commit 8f73cd0

Browse files
committed
JSON serialisation fix for MessageComponent with interactions, Generate new docs and fix gateway url issues
1 parent f39f4b2 commit 8f73cd0

27 files changed

+9625
-4904
lines changed

dimscord/constants.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ type
186186
sfUserSubscription = 8
187187
const
188188
libName* = "Dimscord"
189-
libVer* = "1.6.0"
189+
libVer* = "1.8.0"
190190
libAgent* = "DiscordBot (https://github.com/krisppurg/dimscord, v"&libVer&")"
191191

192192
cdnBase* = "https://cdn.discordapp.com/"

dimscord/gateway.nim

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ proc reconnect(s: Shard) {.async.} =
442442
s.reconnecting = true
443443
s.retry_info.attempts += 1
444444

445-
var url = s.resumeGatewayUrl
445+
var url = if s.resumeGatewayUrl == "": s.gatewayUrl else: s.resumeGatewayUrl
446446
var query = "?v=" & $s.client.gatewayVersion
447447
when defined(discordEtf): query &= "&encoding=etf"
448448
if not url.endsWith("/"): url &= "/"
@@ -577,17 +577,7 @@ proc handleSocketMessage(s: Shard) {.async.} =
577577
var data: JsonNode
578578

579579
when defined(discordCompress):
580-
if packet[0] == Binary:
581-
packet[1] = uncompress(packet[1])
582-
# buffer &= packet[1]
583-
# if len(packet[1]) >= 4:
584-
# if packet[1][^4..^1] == zlib_suffix:
585-
# packet[1] = uncompress(buffer)
586-
# buffer = ""
587-
# else:
588-
# return
589-
# else:
590-
# return
580+
if packet[0] == Binary: packet[1] = uncompress(packet[1])
591581

592582
try:
593583
when defined(discordEtf):
@@ -598,7 +588,7 @@ proc handleSocketMessage(s: Shard) {.async.} =
598588
s.logShard("A zombied connection was detected.")
599589
else:
600590
s.logShard(
601-
"An error occurred while parsing data: "&packet[1]
591+
fmt"Received non-JSON message from gateway: '{packet[1]}'"
602592
)
603593
autoreconnect = s.handleDisconnect(packet[1])
604594

@@ -657,12 +647,10 @@ proc handleSocketMessage(s: Shard) {.async.} =
657647
await s.identify()
658648
else:
659649
discard
660-
if not reconnectable:
661-
raise newException(Exception, "Fatal error occurred.")
662650

663-
if packet[0] == Close:
664-
if not autoreconnect:
665-
autoreconnect = s.handleDisconnect(packet[1])
651+
# if packet[0] == Close:
652+
# if not autoreconnect:
653+
# autoreconnect = s.handleDisconnect(packet[1])
666654

667655
s.stop = true
668656
s.reset()
@@ -674,9 +662,11 @@ proc handleSocketMessage(s: Shard) {.async.} =
674662
if not s.networkError: await s.handleSocketMessage()
675663
else:
676664
let info = extractCloseData(packet[1])
665+
var reason = info.reason
666+
if info.code == 4014: reason &= " Check if your priviliged intents you listed are enabled on the OAuth2 Bot Applications section via https://discord.dev/"
677667
raise newException(
678668
Exception,
679-
"Fatal discord gateway error: "&"["&($info.code)&"] "&info.reason
669+
"Fatal discord gateway error: "&"["&($info.code)&"] "&reason
680670
)
681671

682672
proc endSession*(discord: DiscordClient) {.async.} =
@@ -721,11 +711,9 @@ proc startSession(s: Shard, url, query: string) {.async.} =
721711
# It will try to forcefully put message content intent if not specified.
722712
proc startSession*(discord: DiscordClient,
723713
autoreconnect = true;
724-
gateway_intents: set[GatewayIntent] = {
725-
giGuilds, giGuildMessages,
726-
giDirectMessages, giGuildVoiceStates,
727-
giMessageContent
728-
}; large_message_threshold, large_threshold = 50;
714+
gateway_intents: set[GatewayIntent];
715+
content_intent = true;
716+
large_message_threshold, large_threshold = 50;
729717
max_message_size = 5_000_000;
730718
gateway_version = 10;
731719
max_shards = none int; shard_id = 0;
@@ -734,8 +722,7 @@ proc startSession*(discord: DiscordClient,
734722
## Connects the client to Discord via gateway.
735723
##
736724
## - `gateway_intents` Allows you to subscribe to pre-defined events.
737-
## **NOTE:** When not specified this will default to:
738-
## `giGuilds, giGuildMessages, giDirectMessages, giGuildVoiceStates, giMessageContent`
725+
## - `content_intent` Whether to use discord's priviliged message content on by default. (defaults to `true`)
739726
##
740727
## - `large_threshold` The number that would be considered a large guild (50-250).
741728
## - `guild_subscriptions` **DEPRECATED** Whether or not to receive presence_update, typing_start events.
@@ -750,13 +737,13 @@ proc startSession*(discord: DiscordClient,
750737

751738
discord.autoreconnect = autoreconnect
752739

753-
# assert gateway_intents.len == 0, "Gateway intents cannot be empty."
754740
discord.intents = gateway_intents
755741

756742
if giMessageContent notin discord.intents:
757-
log("Warning: giMessageContent not specified this might cause issues.")
743+
if content_intent: raise newException(Exception, "giMessageContent not in intents, if you wish to turn it off set content_intent = false in startSession")
758744

759745
discord.largeThreshold = large_threshold
746+
760747
if guild_subscriptions:
761748
log("Warning: guild_subscriptions is deprecated.")
762749
discord.intents = discord.intents + {
@@ -768,8 +755,6 @@ proc startSession*(discord: DiscordClient,
768755

769756
discord.max_shards = max_shards.get(-1)
770757
discord.gatewayVersion = gateway_version
771-
# when defined(discordv8):
772-
# discord.gatewayVersion = 8
773758
when defined(discordv9):
774759
discord.gatewayVersion = 9
775760

@@ -782,9 +767,6 @@ proc startSession*(discord: DiscordClient,
782767
when defined(discordEtf):
783768
query &= "&encoding=etf"
784769

785-
# when defined(discordCompress):
786-
# query &= "&compress=zlib-stream"
787-
788770
if discord.shards.len == 0:
789771
log("Starting gateway session.")
790772

dimscord/helpers.nim

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,14 @@ template waitFor*(discord: DiscordClient; event: static[DispatchEvent],
613613
##
614614
## See also:
615615
## - [Events](./objects.html#Events)
616+
##
617+
## Similar procs:
618+
## - [waitForComponentUse]
619+
## - [waitForDeletion]
620+
## - [waitForInternal]
621+
## - [waitForReaction]
622+
## - [waitForReply]
623+
## - [waitToJoinVoice]
616624
block:
617625
# Issue is that we can't refine the handler type to be
618626
# different depending on what event is. To get around this

dimscord/objects.nim

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ proc newDiscordClient*(token: string;
9292
o: Option[Message], exists: bool) {.async.} = discard,
9393
message_reaction_add: proc (s: Shard,
9494
m: Message, u: User,
95-
e: Emoji, exists: bool) {.async.} = discard,
95+
emj: Emoji, exists: bool) {.async.} = discard,
9696
message_reaction_remove: proc (s: Shard, m: Message,
9797
u: User, r: Reaction, exists: bool) {.async.} = discard,
9898
message_reaction_remove_all: proc (s: Shard, m: Message,
@@ -897,6 +897,9 @@ proc `%%*`*(a: ApplicationCommand): JsonNode =
897897
softassert a.name.len in 1..32
898898
# This ternary is needed so that the enums can stay similar to
899899
# the discord api
900+
901+
# <TODO> PLEASE CLEAN UP THE CODE
902+
900903
let commandKind = if a.kind == atNothing: atSlash else: a.kind
901904
result = %*{
902905
"name": a.name,
@@ -1017,6 +1020,8 @@ proc `&=`(a: var JsonNode, b: JsonNode) =
10171020
a = a+b
10181021

10191022
proc `%%*`*(comp: MessageComponent): JsonNode =
1023+
# Fyi, it's named that because originally it was meant to avoid conflicts with json but now since there is no conflicts,
1024+
# I thought I'd just keep it as it is and make a `%` that would redirect the proc.
10201025
result = %*{"type": comp.kind.ord}
10211026

10221027
result.loadOpts(comp, spoiler, placeholder, disabled, id, label, description)
@@ -1058,4 +1063,17 @@ proc `%%*`*(comp: MessageComponent): JsonNode =
10581063
of mctSeparator: result.loadOpts(comp, divider, spacing)
10591064
of mctTextDisplay: result["content"] = %comp.content
10601065
of mctLabel:
1061-
result &= %*{"component": %%*comp.component}
1066+
result &= %*{"component": %%*comp.component}
1067+
1068+
proc `%`*(m: MessageComponent): JsonNode = %%*m
1069+
1070+
proc `%`*(m: InteractionCallbackDataMessage): JsonNode =
1071+
result = %*{
1072+
"content": m.content,
1073+
"embeds": %m.embeds.mapIt(%it),
1074+
"allowed_mentions": %m.allowed_mentions,
1075+
"flags": %m.flags,
1076+
"attachments": %m.attachments,
1077+
"components": %m.components.mapIt(%%*it)
1078+
}
1079+
result.loadOpts(m, tts)

dimscord/objects/typedefs.nim

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ type
401401
party*: Option[tuple[id: string, size: seq[int]]] ## todo
402402
assets*: Option[ActivityAssets]
403403
secrets*: Option[tuple[join, spectate, match: string]]
404-
buttons*: seq[tuple[label, url: string]] # !!!! parsehooks
404+
buttons*: seq[tuple[label, url: string]]
405405
instance*: bool
406406
Presence* = ref object
407407
user*: User
@@ -711,7 +711,7 @@ type
711711
## ```
712712
name*: string
713713
name_localizations*: Option[Table[string, string]]
714-
value*: (Option[string], Option[int]) # !!!!!!!!
714+
value*: (Option[string], Option[int])
715715
MessageInteractionMetadata* = object
716716
id*, name*: string
717717
kind*: InteractionType
@@ -826,7 +826,6 @@ type
826826
flags*: set[MessageFlags]
827827
attachments*: seq[Attachment]
828828
components*: seq[MessageComponent]
829-
# InteractionCallbackDataMessage* = InteractionApplicationCommandCallbackData
830829
InteractionCallbackDataAutocomplete* = object
831830
choices*: seq[ApplicationCommandOptionChoice]
832831
InteractionCallbackDataModal* = object
@@ -1034,12 +1033,36 @@ type
10341033
shards*: int
10351034
session_start_limit*: GatewaySession
10361035
Events* = ref object
1037-
## An object containing events that can be changed.
1036+
## An object consisting of events that can be registered
1037+
##
1038+
## Each property can be written in the following form:
1039+
## ```nim
1040+
## # for interaction_create for instance
1041+
## proc interaction_create(s: Shard, i: Interaction) {.event(discord).} =
1042+
## ...
1043+
##
1044+
## # The {.event(discord).} pragma is a macro which rewrites the expression as this:
1045+
## discord.events.interaction_create = proc interaction_create(s: Shard, i: Interaction) {.async.} =
1046+
## ...
1047+
##
1048+
## # the event name is also case insensitive (except for first letter),
1049+
## # thanks to Nim's flexibility so writing the following would be valid equivalent.
1050+
##
1051+
## proc interactionCreate(s: Shard, i: Interaction) {.event(discord).} =
1052+
## ...
1053+
##
1054+
## # just don't forget the pragma, it also automatically adds in the {.async.} pragma
1055+
## # hence why use of async/await inside proc is valid.
1056+
## ```
1057+
##
1058+
## Additional information:
1059+
## - `exists` (parameter) Checks object is cached or not. Other cachable objects dont have them.
1060+
## - `on_dispatch` (event) gives you the raw event data for you to handle things, useful for debugging. (see last bullet point for information)
1061+
## - `message_reaction_add` (event) to get [Reaction] data you can do `msg.reactions[$emoji]`.
1062+
## - [Discord API docs on gateway events](https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-events)
10381063
##
1039-
## - `exists` Checks message is cached or not. Other cachable objects dont have them.
10401064
##
1041-
## - `on_dispatch` event gives you the raw event data for you to handle things.
1042-
## [For reference](https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-events)
1065+
## - See also [waitFor] in helpers section
10431066
on_dispatch*: proc (s: Shard, evt: string, data: JsonNode) {.async.}
10441067
on_ready*: proc (s: Shard, r: Ready) {.async.}
10451068
on_disconnect*: proc (s: Shard) {.async.}

dimscord/restapi/requester.nim

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,15 @@ proc request*(api: RestApi, meth, endpoint: string;
137137
reason: if audit_reason != "": audit_reason else: ""
138138
))
139139

140-
try:
141-
resp = await client.request(url, parseEnum[HttpMethod](meth),
140+
let req = client.request(url, parseEnum[HttpMethod](meth),
142141
pl, multipart=mp)
142+
143+
if not (await req.withTimeout(20_000)):
144+
log("Request is taking longer than 20s. Retrying request...")
145+
await doreq()
146+
147+
try:
148+
resp = await req
143149
except:
144150
r.processing = false
145151
raise
@@ -218,6 +224,7 @@ proc request*(api: RestApi, meth, endpoint: string;
218224
elif status == Http504:
219225
error = fin & "Gateway timed out."
220226

227+
echo pl.parseJson.pretty()
221228
if fatalErr:
222229
raise DiscordHttpError(
223230
msg: error,

dimscord/restapi/user.nim

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ proc registerApplicationCommand*(api: RestApi; application_id: string;
206206
## overwrite the old command.
207207
softAssert name.len in 1..32
208208
var payload = %*{"name": name,
209-
"type": ord kind}
209+
"type": %(ord kind)}
210210

211211
if default_member_permissions.isSome:
212212
payload["default_member_permissions"] = %(
@@ -350,7 +350,22 @@ proc interactionResponseMessage*(api: RestApi,
350350
kind: InteractionResponseType,
351351
response: InteractionCallbackDataMessage) {.async.} =
352352
## Create an interaction response.
353-
## `response.kind` is required.
353+
## - `response.kind` is required.
354+
##
355+
## Example:
356+
## ```nim
357+
## await discord.api.interactionResponseMessage(
358+
## interaction_id, interaction_token,
359+
## kind = ..., # you can choose whichever
360+
## response = InteractionCallbackDataMessage(
361+
## flags: {mfIsEphemeral, mfIsComponentsV2},
362+
## content: "What's up bro".
363+
## components: @[...],
364+
## ...
365+
## )
366+
## )
367+
## )
368+
## ```
354369
var payload = %*{"type":int kind, "data": newJObject()}
355370
var mpd: MultipartData = nil
356371
if response != nil:

docs/dimscord.html

Lines changed: 5 additions & 4 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)