Skip to content

Commit

Permalink
Add ChatInputEvent handling.
Browse files Browse the repository at this point in the history
  • Loading branch information
danmason committed Oct 2, 2021
1 parent e9cf948 commit d51b3e3
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 19 deletions.
5 changes: 4 additions & 1 deletion project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.10.1"]
[com.discord4j/discord4j-core "3.1.7"]
[com.fasterxml.jackson.core/jackson-core "2.12.5"]
[com.discord4j/discord4j-core "3.2.0"]
[com.discord4j/discord4j-rest "3.2.0"]
[com.discord4j/discord-json "1.6.10" ]
[com.sedmelluq/lavaplayer "1.3.77" ]
[ch.qos.logback/logback-classic "1.2.3"]
[medley "1.3.0"]]
Expand Down
78 changes: 73 additions & 5 deletions src/cljukebox/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,104 @@
[cljukebox.util :as util]
[cljukebox.handlers :as handlers])
(:import [discord4j.core DiscordClient GatewayDiscordClient]
[discord4j.core.object.presence Presence Status Activity]
[discord4j.core.object.presence ClientPresence Status ClientActivity]
[discord4j.discordjson.json ApplicationCommandOptionData ApplicationCommandRequest]
[discord4j.core.object.command ApplicationCommandOption$Type ApplicationCommand]
discord4j.discordjson.json.ApplicationCommandData
discord4j.core.event.domain.lifecycle.ReadyEvent
discord4j.core.event.domain.interaction.ChatInputInteractionEvent
discord4j.core.event.domain.message.MessageCreateEvent
discord4j.rest.RestClient
reactor.core.publisher.Mono)
(:gen-class))

(def !gateway-client (atom nil))

(defn on-message [message-event]
(let [{:keys [guild-id message-channel content] :as data} (util/message-event->map message-event)
(let [{:keys [guild-id content] :as data} (util/message-event->map message-event)
prefix (util/get-prefix guild-id)
handler-fn (handlers/get-handler-fn content prefix)]
(when handler-fn
(handler-fn data))
(Mono/empty)))

(defn on-chat-input [chat-input-event]
(let [{:keys [command args] :as data} (util/chat-input-event->map chat-input-event)
handler-fn (get-in handlers/handlers [command :handler-fn])]
(.subscribe (.reply chat-input-event "Command received"))
(handler-fn data args)
(Mono/empty)))

(defn on-bot-ready [^ReadyEvent ready-event]
(-> ready-event
(.getSelf)
(.getClient)
(.updatePresence (Presence/online (Activity/playing "Type '^help' for a list of commands")))
(.updatePresence (ClientPresence/online (ClientActivity/playing "Type '^help' for a list of commands")))
(.block))
(Mono/empty))

;; Useful for cleanup
(defn remove-all-command-definitions [^RestClient rest-client]
(let [application-id (-> rest-client .getApplicationId .block)
application-service (-> rest-client .getApplicationService)
commands (-> application-service
(.getGlobalApplicationCommands application-id)
(.collectMap (util/as-function (fn [x] (.name x))))
(.block))]
(run!
(fn [[x ^ApplicationCommandData command-data]]
(let [cmd-id (-> (.id command-data) (Long/parseLong))]
(-> application-service
(.deleteGlobalApplicationCommand application-id cmd-id)
(.subscribe))))
commands)))

(defn add-command-definitions [^RestClient rest-client]
(let [application-id (-> rest-client .getApplicationId .block)
application-service (-> rest-client .getApplicationService)]
(run!
(fn [[name {:keys [doc args] :as handler-info}]]
(let [base-command-request (-> (ApplicationCommandRequest/builder)
(.name name)
(.description doc))
args-as-options (mapv
(fn [{:keys [name doc required?]}]
(-> (ApplicationCommandOptionData/builder)
(.name name)
(.description doc)
(.type (.getValue ApplicationCommandOption$Type/STRING))
(.required required?)
(.build)))
args)
command-request (.build (reduce
(fn [cmd-request option-data]
(.addOption cmd-request option-data))
base-command-request
args-as-options))]
(-> application-service
(.createGlobalApplicationCommand application-id command-request)
(.subscribe))))
handlers/handlers)))

(defn handle-client [^GatewayDiscordClient client]
;; Logout client on shutdown
(.addShutdownHook (Runtime/getRuntime)
(Thread. ^Runnable (fn [] (some-> client .logout .block))))

;; Add global application commands to client
(add-command-definitions (.getRestClient client))

;; Handle events
(let [on-login (-> client
(.on ReadyEvent (util/as-function on-bot-ready))
(.then))
on-message (-> client
(.on MessageCreateEvent (util/as-function on-message))
(.then))]
(.and on-login on-message)))
(.then))
on-chat-input (-> client
(.on ChatInputInteractionEvent (util/as-function on-chat-input))
(.then))]
(-> on-login (.and on-message) (.and on-chat-input))))

(defn start-bot! [token]
(-> (DiscordClient/create token)
Expand Down
27 changes: 16 additions & 11 deletions src/cljukebox/handlers.clj
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,29 @@

(defn help-handler
([{:keys [message-channel content] :as data}]
(if-let [command (util/get-arguments content)]
(help-handler data {:command command})
(let [command (util/get-arguments content)]
(help-handler data {:command command})))
([{:keys [message-channel] :as data} {:keys [command] :as opts}]
(if command
(if-let [{:keys [long-doc doc usage-str]} (get base-handlers command)]
(util/send-embed message-channel {:title command
:description (or long-doc doc)
:fields [{:name "Usage Example"
:value (format "`%s`" usage-str)}]})
(util/send-message message-channel (format "*%s* is not an existing command" command)))
(util/send-embed message-channel {:title "Help Menu"
:description "For more information about specific commands, use `help <command>`"
:fields (mapv (fn [[k {:keys [doc]}]]
{:name k
:value doc})
base-handlers)})))
([{:keys [message-channel] :as data} {:keys [command] :as opts}]
(if-let [{:keys [doc usage-str]} (get base-handlers command)]
(util/send-embed message-channel {:title command
:description doc
:fields [{:name "Usage Example"
:value (format "`%s`" usage-str)}]})
(util/send-message message-channel (format "*%s* is not an existing command" command)))))
base-handlers)}))))

(def handlers
(assoc base-handlers "help" {:handler-fn help-handler}))
(assoc base-handlers "help" {:doc "Outputs information about the various commands on the bot"
:args [{:name "command"
:required? false
:doc "Specific command you wish to ask for information on"}]
:handler-fn help-handler}))

(defn get-handler-fn [content prefix]
(some (fn [[k v]]
Expand Down
6 changes: 5 additions & 1 deletion src/cljukebox/handlers/play.clj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
(player/mk-audio-handler scheduler message-channel))))))

(def handler-data
{:doc "Will add audio to the bot's playlist - if not currently playing anything, will join the bot to the calling user's voice channel. For a list of supported sources/file formats, see here: https://github.com/sedmelluq/lavaplayer#supported-formats"
{:doc "Add a track to the bot's playlist"
:long-doc "Add a track to the bot's playlist - if not currently playing anything, will join the bot to the calling user's voice channel. For a list of supported sources/file formats, see here: https://github.com/sedmelluq/lavaplayer#supported-formats"
:usage-str "play <media-url>"
:args [{:name "url"
:doc "URL of the track you want to play"
:required? true}]
:handler-fn play-audio})
5 changes: 4 additions & 1 deletion src/cljukebox/handlers/prefix.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
([{:keys [content message-channel guild-id] :as data}]
(if-let [new-prefix (util/get-arguments content)]
(set-prefix data {:new-prefix new-prefix})
(util/send-message message-channel (format "Command prefix is currently set to `%s`" (util/get-prefix guild-id)))))
(util/send-message message-channel (format "Need to supply new bot prefix (currently set to `%s`)" (util/get-prefix guild-id)))))
([{:keys [message-channel guild-id] :as data} {:keys [new-prefix] :as opts}]
(util/merge-to-config {guild-id {:prefix new-prefix}})
(util/send-message message-channel (format "Command prefix set to `%s`" new-prefix))))

(def handler-data
{:doc "Sets the server wide command prefix (default is `^`)"
:usage-str "prefix <new-prefix>"
:args [{:name "new-prefix"
:doc "New prefix string for the bot to use for commands"
:required? true}]
:handler-fn set-prefix})
20 changes: 20 additions & 0 deletions src/cljukebox/util.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
(:import [java.util.function Consumer Function]
discord4j.core.object.entity.channel.MessageChannel
discord4j.core.event.domain.message.MessageCreateEvent
discord4j.core.object.command.ApplicationCommandInteractionOption
discord4j.core.event.domain.interaction.ChatInputInteractionEvent
discord4j.core.spec.EmbedCreateSpec))

(defn read-config []
Expand Down Expand Up @@ -39,6 +41,24 @@
:member member
:voice-channel (some-> member .getVoiceState .block .getChannel .block)}))

(defn chat-input-event->map [^ChatInputInteractionEvent chat-input-event]
(let [interaction (.getInteraction chat-input-event)
member (some-> interaction .getMember (.orElse nil))]
{:guild-id (some-> interaction .getGuildId (.orElse nil) .asString)
:message-channel (.getChannel interaction)
:member member
:voice-channel (some-> member .getVoiceState .block .getChannel .block)
:command (.getCommandName chat-input-event)
:args (some->> (.getOptions chat-input-event)
(not-empty)
(map (fn [^ApplicationCommandInteractionOption option]
(let [k (keyword (.getName option))
v (some-> (.getValue option)
(.orElse nil)
(.asString))]
[k v])))
(into {}))}))

(defn merge-to-config [m]
(let [current-config @!config
updated-config (medley/deep-merge current-config m)]
Expand Down

0 comments on commit d51b3e3

Please sign in to comment.