Skip to content

Commit

Permalink
Merge pull request #39 from Kyando2/master
Browse files Browse the repository at this point in the history
v0.6.0
  • Loading branch information
xxxAnn authored Feb 5, 2022
2 parents 0b6ddcf + 48826a0 commit 6d1813a
Show file tree
Hide file tree
Showing 16 changed files with 307 additions and 49 deletions.
3 changes: 2 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Dizkord"
uuid = "c8b112a7-7677-4d35-8651-7c5b5cba290e"
authors = ["Kyando <[email protected]>"]
version = "0.5.2"
version = "0.6.0"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand All @@ -28,6 +28,7 @@ LRUCache = "1"
OpenTrick = "0.2"
Setfield = "0.8"
TimeToLive = "0.3"
Documenter = "0.27"

[targets]
test = ["Test"]
1 change: 1 addition & 0 deletions docs/src/helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ plaintext
upload_file
set_game
opt
Option
extops
@fetch
@fetchval
Expand Down
21 changes: 21 additions & 0 deletions src/Dizkord.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,27 @@ function compile(f::Function, force::Bool; kwargs...)
end
end

function method_argnames(m::Method)
argnames = ccall(:jl_uncompress_argnames, Vector{Symbol}, (Any,), m.slot_syms)
isempty(argnames) && return argnames
return argnames[2:m.nargs]
end

function method_argtypes(m::Method)
return m.sig.types[2:end]
end

"""
method_args(m::Method) -> Vector{Tuple{Symbol, Type}}
"""
function method_args(m::Method)
n, t, v = method_argnames(m), method_argtypes(m), []
for x = 1:length(n)
push!(v, (n[x], t[x]))
end
v
end

include(joinpath("utils", "consts.jl"))
include(joinpath("types", "types.jl"))
include(joinpath("client", "client.jl"))
Expand Down
4 changes: 4 additions & 0 deletions src/client/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ mutable struct Client
presence::Dict # Default presence options.
conn::Conn # WebSocket connection.
p_guilds::Dict{Snowflake, String} # Command prefix overrides.
no_auto_ack::Vector{String} # No auto ack custom ids.
auto_update_ack::Vector{String} # Auto ack for deferred message update custom ids.

function Client(
token::String,
Expand Down Expand Up @@ -147,6 +149,8 @@ mutable struct Client
presence, # presence
conn, # conn
Dict(), # p_guilds
[], # no_auto_ack
[] # auto_update_ack
)

return c
Expand Down
116 changes: 100 additions & 16 deletions src/client/handlers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,37 +43,108 @@ Adds a handler for INTERACTION CREATE gateway events where the InteractionData's
Adds this command to `c.commands` or `c.guild_commands` based on the presence of `guild`.
The `f` parameter signature should be:
```
(ctx::Context) -> Any
(ctx::Context, args...) -> Any
```
Where args is a list of all the Command Options
For example a command that takes a user u and a number n as input should have this signature:
```
(ctx::Context, u::User, n::Int) -> Any
```
and the arguments would automatically get converted.
**Note:** The argument names **must** match the Option names. The arguments can be ordered in any way. If no type is specified, no conversion will be performed, so Discord objects will be `Snowflake`s.
"""
function command!(f::Function, c::Client, name::AbstractString, description::AbstractString; kwargs...)
add_handler!(c, OnInteractionCreate(f; name=name))
function command!(c::Client, f::Function, name::AbstractString, desc::AbstractString, legacy::Bool; kwargs...)
haskey(kwargs, :options) && check_option.(kwargs[:options])
namecheck(name, r"^[\w-]{1,32}$", 32, "ApplicationCommand Name")
namecheck(name, 100, "ApplicationCommand Description")
legacy ? add_handler!(c, OnInteractionCreate(f; name=name)) : add_handler!(c, OnInteractionCreate(generate_command_func(f); name=name))
end

function command!(f::Function, c::Client, name::AbstractString, description::AbstractString; legacy::Bool=true, kwargs...)
command!(c, f, name, description, legacy; kwargs...)
add_command!(c; name=name, description=description, kwargs...)
end
function command!(f::Function, c::Client, g::Number, name::AbstractString, description::AbstractString; kwargs...)
add_handler!(c, OnInteractionCreate(f; name=name))
function command!(f::Function, c::Client, g::Number, name::AbstractString, description::AbstractString; legacy::Bool=true, kwargs...)
command!(c, f, name, description, legacy; kwargs...)
add_command!(c, Snowflake(g); name=name, description=description, kwargs...)
end
command!(f::Function, c::Client, g::String, name::AbstractString, description::AbstractString; kwargs...) = command!(f, c, parse(Int, g), name, description; kwargs...)

function check_option(o::ApplicationCommandOption)
namecheck(o.name, r"^[\w-]{1,32}$", 32, "ApplicationCommandOption Name")
namecheck(o.description, 100, "ApplicationCommandOption Description")
end

namecheck(val::String, patt::Regex, len::Int, of::String) = (!occursin(patt, val) || length(val) > len) && throw(NamingError(val, of))
namecheck(val::String, patt::Regex, of::String) = namecheck(val, patt, 32, of)
namecheck(val::String, len::Int, of::String) = namecheck(val, r"", len, of)
namecheck(val::String, of::String) = namecheck(val, 32, of)

function generate_command_func(f::Function)
args = handlerargs(f)
@debug "Generating typed args" args=args
g = (ctx::Context) -> begin
a = makeargs(ctx, args)
f(ctx, a...)
end
g
end

function makeargs(ctx::Context, args)
o = opt(ctx)
v = []
args = args[2:end]
if length(args) == 0
return v
end
for x = args
t = last(x)
arg = get(o, string(first(x)), :ERR)
push!(v, conv(t, arg, ctx))
end
v
end

#// Todo: make converters for User etc
function conv(::Type{T}, arg, ctx::Context) where T
id = snowflake(arg)
if T in [String, Int, Bool]
return arg
elseif T == User
return ctx.interaction.data.resolved.users[id]
elseif T == Member
u = ctx.interaction.data.resolved.users[id]
m = ctx.interaction.data.resolved.members[id]
m.user = u
return m
elseif T == Role
return ctx.interaction.data.resolved.roles[id]
elseif T == DiscordChannel
return ctx.interaction.data.resolved.channels[id]
end
arg
end

"""
component(
f::Function
c::Client
custom_id::AbstractString
kwargs...
)
component!(
f::Function
c::Client
custom_id::AbstractString
kwargs...
)
Adds a handler for INTERACTION CREATE gateway events where the InteractionData's `custom_id` field matches `custom_id`.
The `f` parameter signature should be:
```
(ctx::Context) -> Any
```
"""
function component!(f::Function, c::Client, custom_id::AbstractString; kwargs...)
function component!(f::Function, c::Client, custom_id::AbstractString; auto_ack::Bool=true, auto_update_ack::Bool=true, kwargs...)
add_handler!(c, OnInteractionCreate(f; custom_id=custom_id))
if !auto_ack push!(c.no_auto_ack, custom_id) end
if auto_update_ack push!(c.auto_update_ack, custom_id) end
return Component(custom_id=custom_id; kwargs...)
end
"""
Expand All @@ -87,6 +158,7 @@ Creates an `AbstractContext` based on the event using the `data` provided and pa
"""
function handle(c::Client, handlers::Vector{Handler}, data::Dict, t::Symbol)
ctx = context(t, data::Dict)
hasproperty(ctx, :interaction) && handle_ack(c, ctx)
isvalidcommand = (h) -> return (!hasproperty(h, :name) || (!ismissing(ctx.interaction.data.name) && h.name == ctx.interaction.data.name))
isvalidcomponent = (h) -> return (!hasproperty(h, :custom_id) || (!ismissing(ctx.interaction.data.custom_id) && h.custom_id == ctx.interaction.data.custom_id))
isvalid = (h) -> return isvalidcommand(h) && isvalidcomponent(h)
Expand All @@ -95,15 +167,27 @@ function handle(c::Client, handlers::Vector{Handler}, data::Dict, t::Symbol)
end
end

function handle_ack(c::Client, ctx::Context)
iscomp = !ismissing(ctx.interaction.data.custom_id)
if !iscomp || !(ctx.interaction.data.custom_id in c.no_auto_ack)
@debug "Acking interaction"
if iscomp && (ctx.interaction.data.custom_id in c.auto_update_ack)
update_ack_interaction(c, ctx.interaction.id, ctx.interaction.token)
else
ack_interaction(c, ctx.interaction.id, ctx.interaction.token)
end
end
end

"""
Runs a handler with given context
"""
function runhandler(c::Client, h::Handler, ctx::Context, t::Symbol)
@debug "Running handler" h=Handler type=t
if hasproperty(h, :name) || hasproperty(h, :custom_id)
ack_interaction(c, ctx.interaction.id, ctx.interaction.token)
end
h.f(ctx)
@spawn begin try h.f(ctx) catch e
kws = logkws(c; handler=t, exception=(e, catch_backtrace()))
@warn "Handler errored" kws...
end end
end

"""
Expand Down
4 changes: 3 additions & 1 deletion src/rest/crud/interaction.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
create(c::Client, ::Type{ApplicationCommand}, name::AbstractString, description::AbstractString; kwargs...) = create_application_command(c; name=name, description=description, kwargs...)
create(c::Client, ::Type{ApplicationCommand}, name::AbstractString, description::AbstractString, g::Snowflake; kwargs...) = create_application_command(c, g; name=name, description=description, kwargs...)
create(c::Client, ::Type{Message}, interaction::Interaction; kwargs...) = create_followup_message(c, interaction.id, interaction.token; kwargs...)
create(c::Client, ::Type{Message}, interaction::Interaction; kwargs...) = create_followup_message(c, interaction.id, interaction.token; compkwfix(; kwargs...)...)
create(c::Client, ::Type{Vector{ApplicationCommand}}, cmds::Vector{ApplicationCommand}) = bulk_overwrite_application_commands(c, cmds)
create(c::Client, ::Type{Vector{ApplicationCommand}}, g::Snowflake, cmds::Vector{ApplicationCommand}) = bulk_overwrite_application_commands(c, g, cmds)

retrieve(c::Client, ::Type{Vector{ApplicationCommand}}) = get_application_commands(c)
retrieve(c::Client, ::Type{Vector{ApplicationCommand}}, g::Snowflake) = get_application_commands(c, g)

update(c::Client, ctx::Context, m::Message; kwargs...) = edit_interaction(c, ctx.interaction.token, m.id; compkwfix(; kwargs...)...)
update(c::Client, ctx::Context; kwargs...) = hasproperty(ctx, :interaction) ? update_message_int(c, ctx.interaction.id, ctx.interaction.token; compkwfix(; kwargs...)...) : update(c, ctx.message; compkwfix(; kwargs...)...)
2 changes: 1 addition & 1 deletion src/rest/crud/message.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function create(c::Client, ::Type{Message}, ch::DiscordChannel; kwargs...)
return create_message(c, ch.id; kwargs...)
return create_message(c, ch.id; compkwfix(; kwargs...)...)
end

function retrieve(c::Client, ::Type{Message}, ch::DiscordChannel, message::Integer)
Expand Down
32 changes: 27 additions & 5 deletions src/rest/endpoints/interaction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,43 @@ function respond_to_interaction(c::Client, int_id::Snowflake, int_token::String;
return Response{Message}(c, :POST, "/interactions/$int_id/$int_token/callback"; body=dict)
end

function create_followup_message(c::Client, int_id::Snowflake, int_token::String; kwargs...)
appid = c.application_id
return Response(c, :POST, "/webhooks/$appid/$int_token)"; body=kwargs)
end

function ack_interaction(c::Client, int_id::Snowflake, int_token::String; kwargs...)
dict = Dict{Symbol, Any}(
:type => 5,
)
return Response(c, :POST, "/interactions/$int_id/$int_token/callback"; body=dict)
end

function update_ack_interaction(c::Client, int_id::Snowflake, int_token::String; kwargs...)
dict = Dict{Symbol, Any}(
:type => 6,
)
return Response{Message}(c, :POST, "/interactions/$int_id/$int_token/callback"; body=dict)
end

function update_message_int(c::Client, int_id::Snowflake, int_token::String; kwargs...)
dict = Dict{Symbol, Any}(
:data => kwargs,
:type => 7,
)
return Response(c, :POST, "/interactions/$int_id/$int_token/callback"; body=dict)
end

function create_followup_message(c::Client, int_id::Snowflake, int_token::String; kwargs...)
appid = c.application_id
return Response{Message}(c, :POST, "/webhooks/$appid/$int_token)"; body=kwargs)
end

function edit_interaction(c::Client, int_token::String, mid::Snowflake; kwargs...)
appid = c.application_id
return Response(c, :PATCH, "/webhooks/$appid/$int_token/messages/$mid"; body=kwargs)
end

function bulk_overwrite_application_commands(c::Client, guild::Snowflake, cmds::Vector{ApplicationCommand})
appid = c.application_id
return Response{Vector{ApplicationCommand}}(c, :PUT, "/applications/$appid/guilds/$guild/commands"; body=cmds)
end

function bulk_overwrite_application_commands(c::Client, cmds::Vector{ApplicationCommand})
appid = c.application_id
return Response{Vector{ApplicationCommand}}(c, :PUT, "/applications/$appid/commands"; body=cmds)
Expand Down
2 changes: 1 addition & 1 deletion src/rest/rest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ function Response{T}(
http_r = HTTP.request(args...; status_exception=false)
@debug "Sent http request" Time=now()
if http_r.status - 200 >= 100
@warn "Got an unexpected response" Code=http_r.status Body=http_r Sent=args
@warn "Got an unexpected response" To=args[2] Code=http_r.status Body=http_r Sent=args[4]
end
http_r.status == 429 && return http_r
r = Response{T}(c, http_r)
Expand Down
9 changes: 9 additions & 0 deletions src/types/exception.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
struct NamingError <: Exception
invalid_name::AbstractString
name_of::String
reason::String
end

NamingError(a::AbstractString, nameof::String) = NamingError(a, nameof, "it may be too long or contain invalid characters.")

Base.showerror(io::IO, e::NamingError) = print(io, "Name $(e.invalid_name) is an invalid name for `$(e.name_of)`` because `$(e.reason)`.")
1 change: 1 addition & 0 deletions src/types/handlers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,4 @@ Base.getproperty(h::Handler, sym::Symbol) = sym != :f ? getfield(h, :d)[sym] : g
Base.hasproperty(h::Handler, sym::Symbol) = haskey(getfield(h, :d), sym)

handlerkind(h::Handler) = h.type
handlerargs(f::Function) = method_args(first(methods(f)))
2 changes: 1 addition & 1 deletion src/types/interaction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct ResolvedData
channels::Optional{Dict{Snowflake, Channel}}
messages::Optional{Dict{Snowflake, Message}}
end
@boilerplate ResolvedData :constructors :lower :merge
@boilerplate ResolvedData :constructors :lower :merge

"""
Application Command Choice.
Expand Down
6 changes: 3 additions & 3 deletions src/types/member.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ export Member
A [`Guild`](@ref) member.
More details [here](https://discordapp.com/developers/docs/resources/guild#guild-member-object).
"""
struct Member <: DiscordObject
mutable struct Member <: DiscordObject
user::Optional{User}
nick::OptionalNullable{String} # Not supposed to be nullable.
roles::Vector{Snowflake}
joined_at::DateTime
premium_since::OptionalNullable{DateTime}
deaf::Bool
mute::Bool
deaf::Optional{Bool}
mute::Optional{Bool}
end
@boilerplate Member :constructors :docs :lower :merge :mock

Loading

0 comments on commit 6d1813a

Please sign in to comment.