1
\$\begingroup\$

I made a very bare-bones API "library" in Julia which should abstract the interaction with the API.

The API is fairly complex - it's based around 2 core layers an HTTPS/REST API for general operations, and persistent secure websocket based connection for sending and subscribing to real-time events.

My question here is mainly about the coding style and/or the paradigm which I should be using to approach this "library", I do realize that it's not a library at the moment.

module Juldi

using Requests
using DandelionWebSockets

import Requests: get, post, put, delete, options
import DandelionWebSockets: on_text, state_connecting, state_open, state_closing, state_closed
import JSON

mutable struct DiscordHandler <: WebSocketHandler
    client::WSClient
    stop_channel::Channel{Any}
end

# Global variables?..
TOKEN = "secret"

GATEWAY_OPCODES = Dict("DISPATCH" => 0,
                       "HEARTBEAT" => 1,
                       "IDENTIFY" => 2,
                       "STATUS_UPDATE" => 3,
                       "VOICE_STATE_UPDATE" => 4,
                       "VOICE_SERVER_PING" => 5,
                       "RESUME" => 6, "RECONNECT" => 7,
                       "REQUEST_GUILD_MEMBERS" => 8,
                       "INVALID_SESSION" => 9,
                       "HELLO" => 10,
                       "HEARTBEAT_ACK" => 11
                    )

SEQUENCE = nothing
HEARTBEAT_INTERVAL = nothing

IDENTIFY_DATA = Dict(
    "op" => GATEWAY_OPCODES["IDENTIFY"],
    "d" => Dict(
        "token" => TOKEN,
        "properties" => Dict(
            "\$os" => "linux",
            "\$browser" => "Discord.jl",
            "\$device" => "Discord.jl"
        ),
        "compress" => true,
        "large_threshold" => 250,
        "presence" => Dict(
            "game" => Dict(
                "name" => "Miraris' DMs",
                "type" => 0
            ),
            "status" => "online",
            "afk" => false
        )
    )
)

HEADERS = Dict(
    "Authorization" => "Bot $TOKEN",
    "User-Agent" => "DiscordBot (Discord.jl, 0.0.1)",
    "Content-Type" => "application/json"
)

GATEWAY = Requests.json(get("https://discordapp.com/api/gateway/bot"; headers = HEADERS))

USER = nothing

# Set sequence and other global variables..
function set_globals(response)
    global SEQUENCE
    if response["op"] != 10
        SEQUENCE = response["s"]
    end
end

# Basic send message function..
function send_message(response)
    if isassigned(response["d"]["mentions"], 1) && response["d"]["mentions"][1]["username"] == "Julia"
        channel_id = response["d"]["channel_id"]
        post("https://discordapp.com/api/channels/$channel_id/messages"; headers = HEADERS,
                                                                         json = Dict("content" => "no u"))
    end
end

# Process the incoming payload..
function process_payload(response)
    global USER
    global HEARTBEAT_INTERVAL

    if response["op"] == GATEWAY_OPCODES["DISPATCH"]
        event_name = response["t"]
        data = response["d"]

        if event_name == "READY"
            user = data["user"]
            println("We are ready. I am " * user["username" * user["discriminator"]])
        elseif event_name == "MESSAGE_CREATE"
            send_message(response)
        else
            println("Unhandled Event.")
        end
    elseif response["op"] == GATEWAY_OPCODES["HELLO"]
        HEARTBEAT_INTERVAL = response["d"]["heartbeat_interval"]
        println("Setting HEARTBEAT_INTERVAL to $HEARTBEAT_INTERVAL")
    else
        println("Unhandled OP Code.")
    end
end


function on_text(::DiscordHandler, s::String)
    println("Response: $s")

    response = JSON.parse(s)

    # Set globals..
    set_globals(response)

    # Process the data
    process_payload(response)
end

# Closing state..
state_closing(::DiscordHandler)    = println("State: CLOSING")

# Connecting state..
state_connecting(::DiscordHandler) = println("State: CONNECTING")

# Identify ourselves using OP Code 2
function identify()
    global IDENTIFY_DATA
    println("SENDING IDENTIFY")

    return JSON.json(IDENTIFY_DATA)
end

# On open connection..
function state_open(handler::DiscordHandler)
    println("State: OPEN")

    function send_heartbeat(timer)

        println("SENDING HEARTBEAT")

        heartbeat_packet = JSON.json(Dict(
            "op" => GATEWAY_OPCODES["HEARTBEAT"],
            "d" => SEQUENCE
        ))

        send_text(handler.client, heartbeat_packet)
        println(heartbeat_packet)
        println("HEARTBEAT SENT")
    end

    @schedule begin
        # Identify packet
        send_text(handler.client, identify())

        # Timer for heartbeating
        Timer(send_heartbeat, HEARTBEAT_INTERVAL/1000, HEARTBEAT_INTERVAL/1000)
    end
end

function state_closed(::DiscordHandler)
    println("State: CLOSED")

    # signal that ocnnection is closed..
    put!(stop_chan, true)
end

stop_chan = Channel{Any}(3)

client = WSClient()

handler = DiscordHandler(client, stop_chan)

# Uh, wow - building the ws URI
uri = URI(GATEWAY["url"] * "?v=6&encoding=json")
println("Connecting to $uri.. ")

wsconnect(client, uri, handler)

println("Connected.")

# First message means all messages have been sent and we should close?
take!(stop_chan)

stop(client)

# 2nd means close and we need to exit..
take!(stop_chan)

end # module
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.