Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
Sebastian Crane | b1079768ee | |
Sebastian Crane | 9ed4b34083 | |
Sebastian Crane | 0a939da401 | |
Sebastian Crane | 93306d6d34 | |
Sebastian Crane | bfd321cdb3 | |
Sebastian Crane | 69e154ea87 | |
Sebastian Crane | 7fc472c269 | |
Sebastian Crane | 428c9318fd | |
Sebastian Crane | b099d6bfe1 | |
Sebastian Crane | 6a594c5ad3 | |
Sebastian Crane | 6cce43f137 | |
Sebastian Crane | 91cbdb69da | |
Sebastian Crane | fecd09c656 | |
Sebastian Crane | 0abd793fe7 | |
Sebastian Crane | 56094478db |
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -5,8 +5,24 @@
|
|||
|
||||
`matchbot` uses [version 2.0.0 of the Semantic Versioning](https://semver.org/spec/v2.0.0.html) scheme.
|
||||
|
||||
## [1.1.0] - 2022-05-14
|
||||
|
||||
* Add build system for generating POM, JAR and uberjar (standalone JAR) files
|
||||
|
||||
* Improve code quality
|
||||
|
||||
## [1.0.1] - 2022-04-01
|
||||
|
||||
* Fix vulnerability that causes serialised data to be deleted when it contains certain user input
|
||||
|
||||
* Change internal representation of game names, fixing a potential memory leak for long-running instances
|
||||
|
||||
* Improve reliability and code quality of the startup and shutdown sequences
|
||||
|
||||
## [1.0.0] - 2022-03-03
|
||||
|
||||
* Initial release
|
||||
|
||||
[1.1.0]: https://git.libregaming.org/LibreGaming/matchbot/releases/tag/1.1.0
|
||||
[1.0.1]: https://git.libregaming.org/LibreGaming/matchbot/releases/tag/1.0.1
|
||||
[1.0.0]: https://git.libregaming.org/LibreGaming/matchbot/releases/tag/1.0.0
|
||||
|
|
|
@ -40,3 +40,37 @@ For example:
|
|||
> `;; SPDX-FileCopyrightText: 2022 Joe Bloggs <joe@example.com>`
|
||||
|
||||
If the copyright to your contributions is held by your employer, put your employer's name in brackets after your own name.
|
||||
|
||||
## Build system
|
||||
|
||||
### Building a POM file
|
||||
|
||||
The POM file lists the dependencies needed to run `matchbot` as well as some additional information that can help people learn more about `matchbot`.
|
||||
To generate a POM file, run `clojure -T:build pom`; you should find the generated `POM.xml` file in the `target/` directory.
|
||||
|
||||
### Building a JAR file
|
||||
|
||||
A JAR file contains all the source code of `matchbot` in a form that the JVM can load and pass to the Clojure compiler to run.
|
||||
To generate a JAR file, run `clojure -T:build jar`; again, you should find the JAR file called something like `matchbot-x.x.x.jar` in the `target/` directory.
|
||||
|
||||
### Building an uberjar
|
||||
|
||||
An uberjar is much like a normal JAR file, but comes with all the dependencies of `matchbot` bundled in it.
|
||||
This means that it can run directly on the JVM without Clojure being installed (it contains a copy of the Clojure compiler itself).
|
||||
To generate an uberjar, run `clojure -T:build uber`; you should find the uberjar called something like `matchbot-x.x.x-standalone.jar` in the `target/` directory.
|
||||
Please note that if you distribute an uberjar, you must not only comply with the licence of `matchbot`, but also the licences of all `matchbot`'s dependencies, both transitive and intransitive.
|
||||
|
||||
## Versioning and release process
|
||||
|
||||
### Semantic Versioning
|
||||
|
||||
`matchbot` uses [version 2.0.0 of the Semantic Versioning](https://semver.org/spec/v2.0.0.html) scheme, but there is still ambiguity in what exactly comprises the 'Public API' (used for determining the right part of the version to increment) for something like `matchbot`.
|
||||
This 'Public API' is defined for `matchbot` as everything that is accessible by the end user or administrator of a `matchbot` instance.
|
||||
For example, a change that requires the configuration file to be updated warrants a major version increment because it effects the administrator; however, a change to the structure of the internal namespaces would only require a patch level version increment because it doesn't affect either the administrator or the end user.
|
||||
|
||||
### Release process
|
||||
|
||||
At some point after a new feature has been added to `matchbot` or a bug has been fixed, a release will be made.
|
||||
Once a suitable version increment for the type of changes has been determined, the `CHANGELOG.md` file at the root of the repository will be updated with release notes documenting the changes made in that version.
|
||||
Then, a JAR file and a POM file will be produced using the build system (see above for more information), signed using GPG and finally uploaded to [Maven Central](https://central.sonatype.org/).
|
||||
Currently this process is done by [Sebastian Crane](https://git.libregaming.org/seabass); if you would like to help with making releases, please familiarise yourself with the process (you can try everything locally except upload to Maven Central) and get in contact! 😀
|
||||
|
|
14
README.md
14
README.md
|
@ -55,7 +55,7 @@ clojure -M -m system
|
|||
Running the tests is a similar process to running the main application:
|
||||
|
||||
```bash
|
||||
clojure -M:test -m kaocha.runner
|
||||
clojure -M:test
|
||||
```
|
||||
|
||||
### Starting a development REPL
|
||||
|
@ -66,18 +66,18 @@ Since `matchbot` uses Clojure's [tools.deps library](https://clojure.org/guides/
|
|||
You can create, start and stop an instance of the chatbot process with the functions in the `system` namespace:
|
||||
|
||||
``` clojure
|
||||
;; creating a new instance
|
||||
(def my-instance (atom (system/system)))
|
||||
;; creating a new instance - an empty Var
|
||||
(def my-instance nil)
|
||||
|
||||
;; starting the instance
|
||||
(swap! my-instance system/start)
|
||||
(alter-var-root #'my-instance system/start)
|
||||
|
||||
;; restarting the instance
|
||||
(system/restart my-instance)
|
||||
(system/restart #'my-instance)
|
||||
|
||||
;; stopping and resetting the instance
|
||||
(swap! my-instance stop)
|
||||
(reset! my-instance (system/system))
|
||||
(alter-var-root #'my-instance system/stop)
|
||||
(alter-var-root #'my-instance (constantly nil))
|
||||
```
|
||||
|
||||
Once you are familiar with nREPL, you can additionally use [tools.namespace.repl](https://github.com/clojure/tools.namespace) to make reevaluating (reloading) your changes easier:
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
;; SPDX-License-Identifier: Apache-2.0
|
||||
;; SPDX-FileCopyrightText: 2022 Sebastian Crane <seabass-labrax@gmx.com>
|
||||
|
||||
(ns build
|
||||
(:require [clojure.tools.build.api :as b]
|
||||
[tools-pom.tasks :as pom]))
|
||||
|
||||
(def application 'org.libregaming/matchbot)
|
||||
(def version "1.1.1-SNAPSHOT")
|
||||
(def src-dirs ["src"])
|
||||
(def target-dir "target")
|
||||
(def class-dir (format "%s/%s" target-dir "classes"))
|
||||
(def basis (b/create-basis {:project "deps.edn"}))
|
||||
(def pom-file (format "%s/pom.xml" target-dir))
|
||||
(def jar-file (format "%s/%s-%s.jar" target-dir (name application) version))
|
||||
(def uber-file (format "%s/%s-%s-standalone.jar" target-dir (name application) version))
|
||||
|
||||
(defn clean [_]
|
||||
(b/delete {:path target-dir}))
|
||||
|
||||
(defn uber [_]
|
||||
(b/delete {:path class-dir})
|
||||
(b/copy-dir {:src-dirs src-dirs
|
||||
:target-dir class-dir})
|
||||
(b/compile-clj {:basis basis
|
||||
:src-dirs src-dirs
|
||||
:class-dir class-dir})
|
||||
(b/uber {:class-dir class-dir
|
||||
:uber-file uber-file
|
||||
:basis basis
|
||||
:main 'system}))
|
||||
|
||||
(defn jar [_]
|
||||
(b/delete {:path class-dir})
|
||||
(b/copy-dir {:src-dirs src-dirs
|
||||
:target-dir class-dir})
|
||||
(b/jar {:class-dir class-dir
|
||||
:jar-file jar-file}))
|
||||
|
||||
(defn pom [_]
|
||||
(pom/pom
|
||||
{:lib application
|
||||
:version version
|
||||
:write-pom true
|
||||
:validate-pom true
|
||||
:pom
|
||||
{:description
|
||||
"A chatbot for announcing upcoming matches and finding fellow players, written for the LibreGaming community"
|
||||
:url
|
||||
"https://git.libregaming.org/LibreGaming/matchbot"
|
||||
:licenses
|
||||
[:license
|
||||
{:name "Apache-2.0"
|
||||
:url "https://www.apache.org/licenses/LICENSE-2.0.html"}]
|
||||
:developers
|
||||
[:developer
|
||||
{:id "seabass"
|
||||
:name "Sebastian Crane"
|
||||
:email "seabass-labrax@gmx.com"
|
||||
:organization "LibreGaming"
|
||||
:organization-url "https://libregaming.org"
|
||||
:roles [:role "Maintainer"]
|
||||
:timezone "Europe/London"}]
|
||||
:scm
|
||||
{:url "https://git.libregaming.org/LibreGaming/matchbot"
|
||||
:connection "scm:git:https://git.libregaming.org/LibreGaming/matchbot.git"
|
||||
:developer-connection "scm:git:ssh://git@git.libregaming.org/LibreGaming/matchbot.git"}
|
||||
:issue-management
|
||||
{:system "Gitea"
|
||||
:url "https://git.libregaming.org/LibreGaming/matchbot/issues"}}})
|
||||
(b/copy-file {:src "pom.xml" :target pom-file})
|
||||
(b/delete {:path "pom.xml"}))
|
||||
|
||||
(defn all [_]
|
||||
(jar nil)
|
||||
(uber nil)
|
||||
(pom nil))
|
6
deps.edn
6
deps.edn
|
@ -6,4 +6,8 @@
|
|||
clj-commons/clj-yaml {:mvn/version "0.7.107"}
|
||||
irclj/irclj {:mvn/version "0.5.0-alpha4"}}
|
||||
:aliases {:test {:extra-paths ["test"]
|
||||
:extra-deps {lambdaisland/kaocha {:mvn/version "1.60.972"}}}}}
|
||||
:extra-deps {lambdaisland/kaocha {:mvn/version "1.60.972"}}
|
||||
:main-opts ["-m" "kaocha.runner"]}
|
||||
:build {:deps {io.github.clojure/tools.build {:git/tag "v0.8.1" :git/sha "7d40500"}
|
||||
com.github.pmonks/tools-pom {:mvn/version "1.0.74"}}
|
||||
:ns-default build}}}
|
||||
|
|
12
src/bot.clj
12
src/bot.clj
|
@ -6,15 +6,15 @@
|
|||
[game]
|
||||
[irclj.core]))
|
||||
|
||||
(defn keywordise-game [game]
|
||||
(defn lower-case-game [game]
|
||||
(when (string? game)
|
||||
(keyword (str/lower-case game))))
|
||||
(str/lower-case game)))
|
||||
|
||||
(defn sort-case-insensitive [coll]
|
||||
(sort #(apply compare (map str/lower-case %&)) coll))
|
||||
|
||||
(defn match-string [& {:keys [state game player]}]
|
||||
(as-> (keywordise-game game) x
|
||||
(as-> (lower-case-game game) x
|
||||
(game/get-players-of-game state x)
|
||||
(disj x player)
|
||||
(sort-case-insensitive x)
|
||||
|
@ -22,7 +22,7 @@
|
|||
(str "Anyone ready for " game "? " x)))
|
||||
|
||||
(defn list-players-string [& {:keys [state game]}]
|
||||
(as-> (keywordise-game game) x
|
||||
(as-> (lower-case-game game) x
|
||||
(game/get-players-of-game state x)
|
||||
(sort-case-insensitive x)
|
||||
(map #(str " _" % "_") x)
|
||||
|
@ -46,7 +46,7 @@
|
|||
(str "Games with a list of players: "
|
||||
(str/join
|
||||
", "
|
||||
(sort-case-insensitive (map name (game/get-games state))))))
|
||||
(sort-case-insensitive (game/get-games state)))))
|
||||
|
||||
(defn help-string [& {:keys []}]
|
||||
" !list - show all the games that have a list of players
|
||||
|
@ -59,7 +59,7 @@
|
|||
(let [message-parts (str/split message #"\s")
|
||||
command (if-let [x (first message-parts)] (str/lower-case x) "")
|
||||
game (second message-parts)
|
||||
game-keyword (keywordise-game game)]
|
||||
game-keyword (lower-case-game game)]
|
||||
{:command command
|
||||
:game game
|
||||
:game-keyword game-keyword}))
|
||||
|
|
|
@ -4,23 +4,26 @@
|
|||
(ns system
|
||||
(:require [irc]
|
||||
[clojure.data.json :as json]
|
||||
[clj-yaml.core :as yaml]))
|
||||
[clojure.set :as set]
|
||||
[clj-yaml.core :as yaml])
|
||||
(:gen-class))
|
||||
|
||||
(defn json-data-reader [key value]
|
||||
(if (= key :games)
|
||||
(into (empty value)
|
||||
(map #(hash-map (first %)
|
||||
(set (second %)))
|
||||
value))
|
||||
value))
|
||||
(defn setify-vals [x]
|
||||
(reduce #(assoc %1
|
||||
(first %2)
|
||||
(set (second %2)))
|
||||
{} x))
|
||||
|
||||
(defn process-json [x]
|
||||
(-> (set/rename-keys x {"games" :games})
|
||||
(update :games setify-vals)))
|
||||
|
||||
(defn load-state [f]
|
||||
(try
|
||||
(with-open [datafile (clojure.java.io/reader f)]
|
||||
(json/read datafile
|
||||
:value-fn json-data-reader
|
||||
:key-fn keyword))
|
||||
(catch Exception e nil)))
|
||||
(process-json
|
||||
(try
|
||||
(with-open [datafile (clojure.java.io/reader f)]
|
||||
(json/read datafile))
|
||||
(catch Exception e nil))))
|
||||
|
||||
(defn save-state [f data]
|
||||
(try
|
||||
|
@ -34,12 +37,7 @@
|
|||
(yaml/parse-stream datafile))
|
||||
(catch Exception e nil)))
|
||||
|
||||
(defn system []
|
||||
{:config nil
|
||||
:state nil
|
||||
:irc nil})
|
||||
|
||||
(defn start [system]
|
||||
(defn start [_]
|
||||
(let [config (load-config "config.yaml")
|
||||
state (atom (load-state (:data-file config)))
|
||||
irc (irc/new-irc-connection state config)]
|
||||
|
@ -49,20 +47,16 @@
|
|||
:irc irc}))
|
||||
|
||||
(defn stop [system]
|
||||
(do
|
||||
(save-state
|
||||
(get-in system [:config :data-file])
|
||||
(deref (:state system)))
|
||||
(irclj.core/quit (system :irc))))
|
||||
(save-state
|
||||
(get-in system [:config :data-file])
|
||||
(deref (:state system)))
|
||||
(irclj.core/quit (system :irc)))
|
||||
|
||||
(defn restart [system-atom]
|
||||
(do
|
||||
(swap! system-atom stop)
|
||||
(reset! system-atom (system))
|
||||
(swap! system-atom start)))
|
||||
(defn restart [system-var]
|
||||
(stop (deref system-var))
|
||||
(alter-var-root system-var start))
|
||||
|
||||
(defn -main [& args]
|
||||
(let [main-system (atom (system/system))]
|
||||
(swap! main-system system/start)
|
||||
(let [main-system (system/start nil)]
|
||||
(.addShutdownHook (Runtime/getRuntime)
|
||||
(Thread. (partial swap! main-system stop)))))
|
||||
(Thread. #(stop main-system)))))
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
(:require [clojure.test :refer :all]
|
||||
[bot :refer :all]))
|
||||
|
||||
(def test-state '{:games {:hypothetical-shooter #{"abc" "xyz" "123"}
|
||||
:quasi-rts #{"abc" "123"}
|
||||
:imaginary-rpg #{"xyz" "abc"}}})
|
||||
(def test-state '{:games {"hypothetical-shooter" #{"abc" "xyz" "123"}
|
||||
"quasi-rts" #{"abc" "123"}
|
||||
"imaginary-rpg" #{"xyz" "abc"}}})
|
||||
|
||||
(deftest keywordise-game-test
|
||||
(is (= :quasi-rts
|
||||
(keywordise-game "Quasi-RTS"))))
|
||||
(deftest lower-case-game-test
|
||||
(is (= "quasi-rts"
|
||||
(lower-case-game "Quasi-RTS"))))
|
||||
|
||||
(deftest sort-case-insensitive-test
|
||||
(is (= ["A" "b" "C"]
|
||||
|
@ -38,7 +38,7 @@
|
|||
(list-games-string :state test-state))))
|
||||
|
||||
(deftest split-message-test
|
||||
(is (= {:command "!match" :game "Quasi-Rts" :game-keyword :quasi-rts}
|
||||
(is (= {:command "!match" :game "Quasi-Rts" :game-keyword "quasi-rts"}
|
||||
(split-message "!match Quasi-Rts "))))
|
||||
|
||||
(deftest dispatch-command-test
|
||||
|
@ -46,7 +46,7 @@
|
|||
(is (and
|
||||
(= "Added 123 to the list of players for Imaginary-RPG!"
|
||||
(dispatch-command state-atom "123" "!add Imaginary-RPG"))
|
||||
(= {:games {:hypothetical-shooter #{"abc" "xyz" "123"}
|
||||
:quasi-rts #{"abc" "123"}
|
||||
:imaginary-rpg #{"xyz" "abc" "123"}}}
|
||||
(= {:games {"hypothetical-shooter" #{"abc" "xyz" "123"}
|
||||
"quasi-rts" #{"abc" "123"}
|
||||
"imaginary-rpg" #{"xyz" "abc" "123"}}}
|
||||
@state-atom)))))
|
||||
|
|
|
@ -5,31 +5,31 @@
|
|||
(:require [clojure.test :refer :all]
|
||||
[game :refer :all]))
|
||||
|
||||
(def test-state '{:games {:hypothetical-shooter #{"player-one" "player-two" "player-three"}
|
||||
:quasi-rts #{"player-two" "player-four"}
|
||||
:imaginary-rpg #{"player-one" "player-three" "player-four"}}})
|
||||
(def test-state '{:games {"hypothetical-shooter" #{"player-one" "player-two" "player-three"}
|
||||
"quasi-rts" #{"player-two" "player-four"}
|
||||
"imaginary-rpg" #{"player-one" "player-three" "player-four"}}})
|
||||
|
||||
(deftest get-players-of-game-test
|
||||
(is (=
|
||||
'#{"player-two" "player-four"}
|
||||
(get-players-of-game test-state :quasi-rts))))
|
||||
(get-players-of-game test-state "quasi-rts"))))
|
||||
|
||||
(deftest add-player-of-game-test
|
||||
(is (=
|
||||
'#{"player-one" "player-two" "player-four"}
|
||||
(get-in (add-player-of-game test-state :quasi-rts "player-one") [:games :quasi-rts]))))
|
||||
(get-in (add-player-of-game test-state "quasi-rts" "player-one") [:games "quasi-rts"]))))
|
||||
|
||||
(deftest remove-player-of-game-test
|
||||
(is (=
|
||||
'#{"player-one" "player-three"}
|
||||
(get-in (remove-player-of-game test-state :imaginary-rpg "player-four") [:games :imaginary-rpg]))))
|
||||
(get-in (remove-player-of-game test-state "imaginary-rpg" "player-four") [:games "imaginary-rpg"]))))
|
||||
|
||||
(deftest get-games-test
|
||||
(is (=
|
||||
'#{:hypothetical-shooter :quasi-rts :imaginary-rpg}
|
||||
'#{"hypothetical-shooter" "quasi-rts" "imaginary-rpg"}
|
||||
(set (get-games test-state)))))
|
||||
|
||||
(deftest remove-game-test
|
||||
(is (=
|
||||
'#{:hypothetical-shooter :imaginary-rpg}
|
||||
(set (keys (:games (remove-game test-state :quasi-rts)))))))
|
||||
'#{"hypothetical-shooter" "imaginary-rpg"}
|
||||
(set (keys (:games (remove-game test-state "quasi-rts")))))))
|
||||
|
|
Loading…
Reference in New Issue