Create channel, cache, and split client types into seperate nim file

This commit is contained in:
SeanOMik 2020-06-17 22:18:54 -05:00
parent 947dc6884a
commit b3dc748a64
No known key found for this signature in database
GPG Key ID: FA4D55AC05268A88
4 changed files with 228 additions and 43 deletions

29
src/cache.nim Normal file
View File

@ -0,0 +1,29 @@
import sequtils, message, member, channel, guild, discordobject, nimcordutils, clientobjects, httpcore
type Cache* = ref object
members*: seq[GuildMember]
messages*: seq[Message]
channels*: seq[Channel]
guilds*: seq[Guild]
proc getMessageChannel*(msg: Message, cache: Cache): Channel =
for index, channel in cache.channels:
if (channel.id == msg.channelID):
return channel
return nil
proc getChannelGuild*(channel: Channel, cache: Cache): Guild =
for index, guild in cache.guilds:
if (guild.id == channel.guildID):
return guild
return nil
proc getChannel*(cache: Cache, id: snowflake): Channel =
for index, channel in cache.channels:
if (channel.id == id):
return channel
return newChannel(sendRequest(endpoint("/channels/" & $id), HttpGet,
defaultHeaders(), id, RateLimitBucketType.channel))

141
src/channel.nim Normal file
View File

@ -0,0 +1,141 @@
import json, discordobject, user, options, nimcordutils, message, httpcore, asyncdispatch, asyncfutures, strutils
type
ChannelType* = enum
chanTypeNIL = -1,
chanTypeGuildText = 0,
chanTypeDM = 1,
chanTypeGuildVoice = 2,
chanTypeGroupDM = 3,
chanTypeGuildCategory = 4,
chanTypeGuildNews = 5,
chanTypeGuildStore = 6
Channel* = ref object of DiscordObject
`type`*: ChannelType ## The type of channel.
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).
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
lastPinTimestamp: string ## When the last pinned message was pinned
ChannelModify* {.requiresInit.} = ref object
## Use this type to modify a channel by setting each fields.
name*: Option[string]
`type`*: Option[ChannelType]
position*: Option[int]
topic*: Option[string]
nsfw*: Option[bool]
rateLimitPerUser*: Option[int]
bitrate*: Option[int]
userLimit*: Option[int]
#permissionOverwrites*: seq[Permissions] ## Explicit permission overwrites for members and roles.
parentID*: Option[snowflake]
#[ proc newChannelModify*(name: Option[string], `type`: Option[ChannelType], position: Option[int],
topic: Option[string], nsfw: Option[bool], rateLimitPerUser: Option[int], bitrate: Option[int],
userLimit: Option[int], parentID: Option[snowflake]): ChannelModify =
return ChannelModify(name: name.get, `type`:`type`, position: position, nsfw: nsfw,
rateLimitPerUser: rateLimitPerUser, bitrate: bitrate, userLimit: userLimit, parentID: parentID) ]#
proc newChannel*(channel: JsonNode): Channel {.inline.} =
var chan = Channel(
id: getIDFromJson(channel["id"].getStr()),
`type`: ChannelType(channel["type"].getInt()),
)
if (channel.contains("guild_id")):
chan.guildID = getIDFromJson(channel["guild_id"].getStr())
if (channel.contains("position")):
chan.position = channel["position"].getInt()
if (channel.contains("permission_overwrites")):
echo "permission_overwrites"
if (channel.contains("name")):
chan.name = channel["name"].getStr()
if (channel.contains("topic")):
chan.topic = channel["topic"].getStr()
if (channel.contains("nsfw")):
chan.nsfw = channel["nsfw"].getBool()
if (channel.contains("last_message_id")):
chan.lastMessageID = getIDFromJson(channel["last_message_id"].getStr())
if (channel.contains("bitrate")):
chan.bitrate = channel["bitrate"].getInt()
if (channel.contains("user_limit")):
chan.userLimit = channel["user_limit"].getInt()
if (channel.contains("rate_limit_per_user")):
chan.rateLimitPerUser = channel["rate_limit_per_user"].getInt()
if (channel.contains("recipients")):
for recipient in channel["recipients"]:
chan.recipients.insert(newUser(recipient))
if (channel.contains("icon")):
chan.icon = channel["icon"].getStr()
if (channel.contains("owner_id")):
chan.ownerID = getIDFromJson(channel["owner_id"].getStr())
if (channel.contains("application_id")):
chan.applicationID = getIDFromJson(channel["application_id"].getStr())
if (channel.contains("parent_id")):
chan.parentID = getIDFromJson(channel["parent_id"].getStr())
if (channel.contains("last_pin_timestamp")):
chan.lastPinTimestamp = channel["last_pin_timestamp"].getStr()
return chan
proc sendMessage*(channel: Channel, content: string, tts: bool = false): Message =
let messagePayload = %*{"content": content, "tts": tts}
return newMessage(sendRequest(endpoint("/channels/" & $channel.id & "/messages"), HttpPost,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})), channel.id,
RateLimitBucketType.channel, messagePayload))
proc modifyChannel*(channel: Channel, modify: ChannelModify): Future[Channel] {.async.} =
var modifyPayload = %*{}
if (modify.name.isSome):
modifyPayload.add("name", %modify.name.get())
if (modify.`type`.isSome):
modifyPayload.add("type", %modify.`type`.get())
if (modify.position.isSome):
modifyPayload.add("position", %modify.position.get())
if (modify.topic.isSome):
modifyPayload.add("topic", %modify.topic.get())
if (modify.nsfw.isSome):
modifyPayload.add("nsfw", %modify.nsfw.get())
if (modify.rateLimitPerUser.isSome):
modifyPayload.add("rate_limit_per_user", %modify.rateLimitPerUser.get())
if (modify.bitrate.isSome):
modifyPayload.add("bitrate", %modify.bitrate.get())
if (modify.userLimit.isSome):
modifyPayload.add("user_limit", %modify.userLimit.get())
#[ if (modify.name.isSome):
modifyPayload.add("permission_overwrites", %modify.parentID.get()0 ]#
if (modify.parentID.isSome):
modifyPayload.add("parent_id", %modify.parentID.get())
return newChannel(sendRequest(endpoint("/channels/" & $channel.id), HttpPatch,
defaultHeaders(newHttpHeaders({"Content-Type": "application/json"})),
channel.id, RateLimitBucketType.channel, modifyPayload))
proc deleteChannel*(channel: Channel) {.async.} =
discard sendRequest(endpoint("/channels/" & $channel.id), HttpDelete,
defaultHeaders(), channel.id, RateLimitBucketType.channel)

View File

@ -1,4 +1,11 @@
import websocket, asyncnet, asyncdispatch, json, httpClient, eventdispatcher, strformat, eventhandler, streams, nimcordutils, discordobject import websocket, asyncnet, asyncdispatch, json, httpClient, eventdispatcher, strformat
import eventhandler, streams, nimcordutils, discordobject, user, cache, clientobjects
import strutils, channel, options
const
nimcordMajor = 0
nimcordMinor = 0
nimcordMicro = 0
type type
DiscordOpCode = enum DiscordOpCode = enum
@ -14,41 +21,12 @@ type
opHello = 10, opHello = 10,
opHeartbeatAck = 11 opHeartbeatAck = 11
DiscordClient* = ref object ## Discord Client
token*: string
#user*: User
#cache: Cache
ws: AsyncWebSocket
httpClient: AsyncHttpClient
heartbeatInterval: int
heartbeatAcked: bool
lastSequence: int
var globalClient: DiscordClient
proc defaultHeaders*(client: DiscordClient, added: HttpHeaders = newHttpHeaders()): HttpHeaders = proc defaultHeaders*(client: DiscordClient, added: HttpHeaders = newHttpHeaders()): HttpHeaders =
added.add("Authorization", fmt("Bot {client.token}")) added.add("Authorization", fmt("Bot {client.token}"))
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 sendRequest*(endpoint: string, httpMethod: HttpMethod, headers: HttpHeaders, objectID: snowflake = 0, bucketType: RateLimitBucketType = global, jsonBody: JsonNode = %*{}): JsonNode =
var client = newHttpClient()
# Add headers
client.headers = headers
var strPayload: string
if ($jsonBody == "{}"):
strPayload = ""
else:
strPayload = $jsonBody
echo "Sending GET request, URL: ", endpoint, ", body: ", strPayload
waitForRateLimits(objectID, bucketType)
return handleResponse(client.request(endpoint, httpMethod, strPayload), objectId, bucketType)
proc sendGatewayRequest*(client: DiscordClient, request: JsonNode, msg: string = "") {.async.} = proc sendGatewayRequest*(client: DiscordClient, request: JsonNode, msg: string = "") {.async.} =
if (msg.len == 0): if (msg.len == 0):
echo "Sending gateway payload: ", request echo "Sending gateway payload: ", request
@ -96,16 +74,11 @@ proc handleWebsocketPacket(client: DiscordClient) {.async.} =
of ord(DiscordOpCode.opHeartbeatAck): of ord(DiscordOpCode.opHeartbeatAck):
client.heartbeatAcked = true client.heartbeatAcked = true
of ord(DiscordOpCode.opDispatch): of ord(DiscordOpCode.opDispatch):
handleDiscordEvent(json["d"], json["t"].getStr()) discard handleDiscordEvent(client, json["d"], json["t"].getStr())
else: else:
discard discard
proc startConnection*(client: DiscordClient) {.async.} = proc startConnection*(client: DiscordClient) {.async.} =
globalClient = client
client.httpClient = newAsyncHttpClient()
client.httpClient.headers = newHttpHeaders({"Authorization": fmt("Bot {client.token}")})
let urlResult = sendRequest(endpoint("/gateway/bot"), HttpMethod.HttpGet, client.defaultHeaders()) let urlResult = sendRequest(endpoint("/gateway/bot"), HttpMethod.HttpGet, client.defaultHeaders())
if (urlResult.contains("url")): if (urlResult.contains("url")):
let url = urlResult["url"].getStr() let url = urlResult["url"].getStr()
@ -122,6 +95,14 @@ proc startConnection*(client: DiscordClient) {.async.} =
e.msg = "Failed to get gateway url, token may of been incorrect!" e.msg = "Failed to get gateway url, token may of been incorrect!"
raise e raise e
proc newDiscordClient(tkn: string): DiscordClient =
globalToken = tkn
var cac: Cache
new(cac)
result = DiscordClient(token: tkn, cache: cac)
var tokenStream = newFileStream("token.txt", fmRead) var tokenStream = newFileStream("token.txt", fmRead)
var tkn: string var tkn: string
if (not isNil(tokenStream)): if (not isNil(tokenStream)):
@ -130,21 +111,45 @@ if (not isNil(tokenStream)):
tokenStream.close() tokenStream.close()
var bot = DiscordClient(token: tkn) var bot = newDiscordClient(tkn)
registerEventListener(EventType.evtReady, proc(bEvt: BaseEvent) = registerEventListener(EventType.evtReady, proc(bEvt: BaseEvent) =
let event = ReadyEvent(bEvt) let event = ReadyEvent(bEvt)
echo "Ready and connected 1!" bot.clientUser = event.clientUser
)
registerEventListener(EventType.evtReady, proc(bEvt: BaseEvent) = echo "Ready! (v", nimcordMajor, ".", nimcordMinor, ".", nimcordMicro, ")"
let event = ReadyEvent(bEvt) echo "Logged in as: ", bot.clientUser.username, "#", bot.clientUser.discriminator
echo "Ready and connected 2!" echo "ID: ", bot.clientUser.id
echo "--------------------"
) )
registerEventListener(EventType.evtMessageCreate, proc(bEvt: BaseEvent) = registerEventListener(EventType.evtMessageCreate, proc(bEvt: BaseEvent) =
let event = MessageCreateEvent(bEvt) let event = MessageCreateEvent(bEvt)
echo "Message was created!"
if (event.message.content == "?ping"):
var channel: Channel = event.message.getMessageChannel(event.client.cache)
if (channel != nil):
discard channel.sendMessage("PONG")
elif (event.message.content.startsWith("?modifyChannelTopic")):
let modifyTopic = event.message.content.substr(20)
var channel: Channel = event.message.getMessageChannel(event.client.cache)
if (channel != nil):
discard channel.sendMessage("Modifing Channel!")
discard channel.modifyChannel(ChannelModify(topic: some(modifyTopic)))
elif (event.message.content.startsWith("?deleteChannel")):
let channelID = getIDFromJson(event.message.content.substr(15))
var channel: Channel = event.client.cache.getChannel(channelID)
if (channel != nil):
discard channel.sendMessage("Deleting Channel!")
discard channel.deleteChannel()
discard channel.sendMessage("Deleted Channel!")
) )
#[ registerEventListener(EventType.evtGuildCreate, proc(bEvt: BaseEvent) =
let event = GuildCreateEvent(bEvt)
echo "Guild has ", event.guild.members.len, " members and ", event.guild.channels.len, " channels..."
) ]#
waitFor bot.startConnection() waitFor bot.startConnection()

10
src/clientobjects.nim Normal file
View File

@ -0,0 +1,10 @@
import websocket, cache, user
type DiscordClient* = ref object ## Discord Client
token*: string
clientUser*: User
cache*: Cache
ws*: AsyncWebSocket
heartbeatInterval*: int
heartbeatAcked*: bool
lastSequence*: int