Giter Site home page Giter Site logo

johnnyjayjay / slash Goto Github PK

View Code? Open in Web Editor NEW
11.0 2.0 3.0 98 KB

A small Clojure library for handling Discord interactions (slash commands and components)

License: MIT License

Clojure 100.00%
discord-api clojure discord slash-commands hacktoberfest

slash's Introduction

slash

A small Clojure library designed to handle and route Discord interactions, both for gateway events and incoming webhooks.

slash is environment-agnostic, extensible through middleware and works directly with Clojure data (no JSON parsing/printing included).

Clojars Project

slash is currently in a Proof-of-Concept-phase and more features are to be added.
Such features include:

  • Add more middleware: argument validation, permission checks, ...

Command Structure Definition

slash provides utilities to define slash commands in slash.command.structure.

Once you are familiar with how slash commands are structured, the functions should be self-explanatory.

Examples:

(require '[slash.command.structure :refer :all])

(def input-option (option "input" "Your input" :string :required true))

(def echo-command
  (command
   "echo"
   "Echoes your input"
   :options
   [input-option]))

(def fun-commands
  (command
   "fun"
   "Fun commands"
   :options
   [(sub-command
     "reverse"
     "Reverse the input"
     :options
     [input-option
      (option "words" "Reverse words instead of characters?" :boolean)])
    (sub-command
     "mock"
     "Spongebob-mock the input"
     :options
     [input-option])]))

Component Structure Definition

slash also provides similar utilities to create message components.

Examples:

(require '[slash.component.structure :refer :all])

(def my-components
  [(action-row
    (button :danger "unsubscribe" :label "Turn notifications off")
    (button :success "subcribe" :label "Turn notifications on"))
   (action-row
    (select-menu
     "language"
     [(select-option "English" "EN" :emoji {:name "๐Ÿ‡ฌ๐Ÿ‡ง"})
      (select-option "French" "FR" :emoji {:name "๐Ÿ‡ซ๐Ÿ‡ท"})
      (select-option "Spanish" "ES" :emoji {:name "๐Ÿ‡ช๐Ÿ‡ธ"})]
     :placeholder "Language"))])

Routing

You can use slash to handle interaction events based on their type.

(slash.core/route-interaction handler-map interaction-event)

handler-map is a map containing handlers for the different types of interactions that may occur. E.g.

{:ping ping-handler
 :application-command command-handler
 :message-component component-handler}

You can find default handler maps for both gateway and webhook environments in slash.gateway/slash.webhook respectively.

Commands

slash offers further routing middleware and utilities specifically for slash commands. The API is heavily inspired by compojure.

Simple, single-command example:

(require '[slash.command :as cmd] 
         '[slash.response :as rsp :refer [channel-message ephemeral]]) ; The response namespace provides utility functions to create interaction responses

(cmd/defhandler echo-handler
  ["echo"] ; Command path
  _interaction ; Interaction binding - whatever you put here will be bound to the entire interaction
  [input] ; Command options - can be either a vector or a custom binding (symbol, map destructuring, ...)
  (channel-message {:content input}))

You can now use echo-handler as a command handler to call with a command interaction event and it will return the response if it is an echo command or nil if it's not.

An example with multiple (sub-)commands:

(require '[clojure.string :as str])

(cmd/defhandler reverse-handler
  ["reverse"]
  _
  [input words]
  (channel-message
   {:content (if words
               (->> #"\s+" (str/split input) reverse (str/join " "))
               (str/reverse input))}))

(cmd/defhandler mock-handler
  ["mock"]
  _
  [input]
  (channel-message
   {:content (->> input
                  (str/lower-case)
                  (map #(cond-> % (rand-nth [true false]) Character/toUpperCase))
                  str/join)}))
                  
(cmd/defhandler unknown-handler
  [unknown] ; Placeholders can be used in paths too
  {{{user-id :id} :user} :member} ; Using the interaction binding to get the user who ran the command
  _ ; no options
  (-> (channel-message {:content (str "I don't know the command `" unknown "`, <@" user-id ">.")})
      ephemeral))
      
(cmd/defpaths command-paths
  (cmd/group ["fun"] ; common prefix for all following commands
    reverse-handler 
    mock-hander
    unknown-handler))

Similar to the previous example, command-paths can now be used as a command handler. It will call each of its nested handlers with the interaction and stop once a handler is found that does not return nil.

Autocomplete

You can also use the command routing facilities to provide autocomplete for your commands.

;; Will produce autocompletion for command `/foo bar` on option `baz`, using the partial value of `baz` in the process
(cmd/defhandler foo-bar-autocompleter
  ["foo" "bar"]
  {{:keys [focused-option]} :data}
  [baz]
  (case focused-option 
    :baz (rsp/autocomplete-result (map (partial str baz) [1 2 3]))))

Full Webhook Example

For this example, I use the ring webserver specification.

Using ring-json and ring-discord-auth we can create a ring handler for accepting outgoing webhooks.

(require '[slash.webhook :refer [webhook-defaults]]
         '[ring-discord-auth.ring :refer [wrap-authenticate]]
         '[ring.middleware.json :refer [wrap-json-body wrap-json-response]])

(def ring-handler
  (-> (partial slash.core/route-interaction
               (assoc webhook-defaults :application-command command-paths))
      wrap-json-response
      (wrap-json-body {:keyword? true})
      (wrap-authenticate "application public key")))

Full Gateway Example

For this example, I use discljord.

You also see the use of the wrap-response-return middleware for the interaction handler, which allows you to simply return the interaction responses from your handlers and let the middleware respond via REST. You only need to provide a callback that specifies how to respond to the interaction (as I'm using discljord here, I used its functions for this purpose).

(require '[discljord.messaging :as rest]
         '[discljord.connections :as gateway]
         '[discljord.events :as events]
         '[clojure.core.async :as a]
         '[slash.gateway :refer [gateway-defaults wrap-response-return]])

(let [rest-conn (rest/start-connection! "bot token")
      event-channel (a/chan 100)
      gateway-conn (gateway/connect-bot! "bot token" event-channel :intents #{})
      event-handler (-> slash.core/route-interaction
                        (partial (assoc gateway-defaults :application-command command-paths))
                        (wrap-response-return (fn [id token {:keys [type data]}]
                                                (rest/create-interaction-response! rest-conn id token type :data data))))]
  (events/message-pump! event-channel (partial events/dispatch-handlers {:interaction-create [#(event-handler %2)]})))

This is a very quick and dirty example. More in-depth documentation and tutorials will follow soon.

clj-kondo support for macros

You can find a clj-kondo config that gets rid of "unresolved symbol" warnings in .clj-kondo/. Just copy the hooks to your clj-kondo config folder (preserving the directory structure, of course!) and add this to your config.edn:

{:hooks {:analyze-call {slash.command/handler hooks.slash/handler
                        slash.command/defhandler hooks.slash/defhandler
                        slash.command/group hooks.slash/group
                        slash.command/defpaths hooks.slash/defpaths}}}

License

Copyright ยฉ 2021-2023 JohnnyJayJay

Licensed under the MIT license.

slash's People

Contributors

johnnyjayjay avatar jake-moss avatar kiranshila avatar

Stargazers

bitsikka avatar Stefan Kuznetsov avatar Michael Richards avatar Matthew Stead avatar Tim avatar  avatar Jimmy avatar Benjamin avatar  avatar Eloise Christian avatar Daniel Scherf avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.