first commit
This commit is contained in:
commit
5692aa6d1d
70 changed files with 13939 additions and 0 deletions
167
discord-interactions/main.odin
Normal file
167
discord-interactions/main.odin
Normal file
|
@ -0,0 +1,167 @@
|
|||
package discord_interactions
|
||||
|
||||
import "core:crypto/ed25519"
|
||||
import "core:encoding/hex"
|
||||
import "core:encoding/json"
|
||||
import "core:fmt"
|
||||
import "core:io"
|
||||
import "core:log"
|
||||
import "core:net"
|
||||
import "core:os"
|
||||
import REST "../discord/rest"
|
||||
import http "../odin-http"
|
||||
import "../odin-http/client"
|
||||
|
||||
Interaction :: REST.Interaction
|
||||
Payload :: REST.Payload
|
||||
Webhook_Payload :: REST.Webhook_Payload
|
||||
|
||||
ReqResPair :: struct {
|
||||
req: ^http.Request,
|
||||
res: ^http.Response,
|
||||
}
|
||||
|
||||
Config :: struct {
|
||||
token: string,
|
||||
port: int,
|
||||
interaction_endpoint: string,
|
||||
}
|
||||
|
||||
State :: struct {
|
||||
config: Config,
|
||||
application: REST.Application,
|
||||
command_handlers: map[string]Command_Handler,
|
||||
}
|
||||
|
||||
Command_Handler :: #type proc(interaction: Interaction) -> Payload
|
||||
|
||||
Error :: union {
|
||||
os.Error,
|
||||
json.Error,
|
||||
json.Unmarshal_Error,
|
||||
client.Error,
|
||||
client.Body_Error,
|
||||
}
|
||||
|
||||
DISCORD_API :: "https://discord.com/api/v10"
|
||||
|
||||
state: State
|
||||
|
||||
load_config :: proc(filename: string) -> Error {
|
||||
config_bytes, config_read_err := os.read_entire_file_or_err("config.json")
|
||||
if config_read_err != nil {
|
||||
return config_read_err
|
||||
}
|
||||
|
||||
json.unmarshal(config_bytes, &state.config) or_return
|
||||
|
||||
headers: http.Headers
|
||||
http.headers_set(&headers, "Authorization", fmt.tprint("Bot", state.config.token))
|
||||
|
||||
request: client.Request
|
||||
client.request_init(&request, .Get)
|
||||
defer client.request_destroy(&request)
|
||||
request.headers = headers
|
||||
|
||||
res, res_err := client.request(&request, DISCORD_API + "/applications/@me")
|
||||
if res_err != nil {
|
||||
return res_err
|
||||
}
|
||||
|
||||
body, was_alloc, body_err := client.response_body(&res)
|
||||
if body_err != nil {
|
||||
return body_err
|
||||
}
|
||||
|
||||
#partial switch v in body {
|
||||
case client.Body_Plain:
|
||||
json.unmarshal(transmute([]u8)v, &state.application) or_return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
serve :: proc() -> net.Network_Error {
|
||||
s: http.Server
|
||||
http.server_shutdown_on_interrupt(&s)
|
||||
|
||||
router: http.Router
|
||||
http.router_init(&router)
|
||||
defer http.router_destroy(&router)
|
||||
|
||||
http.route_post(&router, state.config.interaction_endpoint, http.handler(interactions))
|
||||
|
||||
routed := http.router_handler(&router)
|
||||
|
||||
port := state.config.port == 0 ? 8080 : state.config.port
|
||||
log.infof("Listening at http://{}:{}", net.address_to_string(net.IP4_Loopback), port)
|
||||
http.listen_and_serve(&s, routed, net.Endpoint{address = net.IP4_Loopback, port = port}) or_return
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
register_command :: proc(command_name: string, handler: Command_Handler) {
|
||||
state.command_handlers[command_name] = handler
|
||||
}
|
||||
|
||||
@(private)
|
||||
interactions :: proc(req: ^http.Request, res: ^http.Response) {
|
||||
pair := new(ReqResPair)
|
||||
pair.req = req
|
||||
pair.res = res
|
||||
http.headers_set_close(&res.headers)
|
||||
|
||||
http.body(req, -1, pair, proc(userdata: rawptr, body: http.Body, err: http.Body_Error) {
|
||||
reqres := cast(^ReqResPair)userdata
|
||||
req := reqres.req
|
||||
res := reqres.res
|
||||
|
||||
interaction: Interaction
|
||||
json.unmarshal(transmute([]u8)body, &interaction)
|
||||
|
||||
signature, signature_ok := hex.decode(
|
||||
transmute([]u8)http.headers_get(req.headers, "X-Signature-Ed25519"),
|
||||
)
|
||||
if !signature_ok {
|
||||
log.error("Failed to decode signature")
|
||||
return
|
||||
}
|
||||
|
||||
timestamp := http.headers_get(req.headers, "X-Signature-Timestamp")
|
||||
|
||||
ed25519_public_key: ed25519.Public_Key
|
||||
public_key_bytes, public_key_ok := hex.decode(transmute([]u8)state.application.verify_key)
|
||||
if !public_key_ok {
|
||||
log.error("Failed to decode public key")
|
||||
return
|
||||
}
|
||||
|
||||
ed25519.public_key_set_bytes(&ed25519_public_key, public_key_bytes)
|
||||
if !ed25519.verify(
|
||||
&ed25519_public_key,
|
||||
transmute([]u8)fmt.tprintf("{}{}", timestamp, body),
|
||||
signature,
|
||||
) {
|
||||
http.respond_with_status(res, .Unauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
switch interaction.type {
|
||||
case 1:
|
||||
if err := http.respond_json(res, Payload{type = 1}); err != nil {
|
||||
log.error("Failed to marshal payload:", err)
|
||||
}
|
||||
return
|
||||
case 2:
|
||||
handler, found := state.command_handlers[interaction.data.name]
|
||||
if found {
|
||||
if err := http.respond_json(res, handler(interaction)); err != nil {
|
||||
log.error("Failed to marshal payload:", err)
|
||||
}
|
||||
} else {
|
||||
log.debug("Skipping unrecognized command:", interaction.data.name)
|
||||
}
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue