aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Audron <audron@cocaine.farm>2021-05-15 13:58:01 +0200
committerMax Audron <audron@cocaine.farm>2021-05-15 13:58:01 +0200
commitd0bff910b0b038ee85bc285bef7a63870a3474ab (patch)
treeb840d0b932c080fa5d5053b6bf66a91f94a80481
init
Diffstat (limited to '')
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock1174
-rw-r--r--Cargo.toml31
-rw-r--r--macros/Cargo.lock7
-rw-r--r--macros/Cargo.toml12
-rw-r--r--macros/src/lib.rs158
-rw-r--r--macros/src/macro_types/mod.rs249
-rw-r--r--rustfmt.toml1
-rw-r--r--src/config.rs46
-rw-r--r--src/hooks/mod.rs39
-rw-r--r--src/hooks/sed.rs175
-rw-r--r--src/hooks/shifty_eyes.rs46
-rw-r--r--src/lib.rs70
-rw-r--r--src/main.rs38
14 files changed, 2048 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f182a35
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+config.json
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..f299a92
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,1174 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
+
+[[package]]
+name = "catinator"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "base64",
+ "futures",
+ "irc",
+ "irc-proto",
+ "macros",
+ "regex",
+ "sasl",
+ "sedregex",
+ "serde",
+ "tokio",
+ "toml",
+ "tracing",
+ "tracing-futures",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec1028182c380cc45a2e2c5ec841134f2dfd0f8f5f0a5bcd68004f81b5efdf4"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "encoding"
+version = "0.2.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
+dependencies = [
+ "encoding-index-japanese",
+ "encoding-index-korean",
+ "encoding-index-simpchinese",
+ "encoding-index-singlebyte",
+ "encoding-index-tradchinese",
+]
+
+[[package]]
+name = "encoding-index-japanese"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-korean"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-simpchinese"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-singlebyte"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding-index-tradchinese"
+version = "1.20141219.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
+dependencies = [
+ "encoding_index_tests",
+]
+
+[[package]]
+name = "encoding_index_tests"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
+
+[[package]]
+name = "futures"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23"
+
+[[package]]
+name = "futures-task"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
+
+[[package]]
+name = "futures-util"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "proc-macro-hack",
+ "proc-macro-nested",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hmac"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
+dependencies = [
+ "crypto-mac",
+ "digest",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "irc"
+version = "0.15.0"
+dependencies = [
+ "chrono",
+ "encoding",
+ "futures-util",
+ "irc-proto",
+ "log",
+ "parking_lot",
+ "pin-project",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "thiserror",
+ "tokio",
+ "tokio-rustls",
+ "tokio-stream",
+ "tokio-util",
+ "webpki-roots",
+]
+
+[[package]]
+name = "irc-proto"
+version = "0.15.0"
+dependencies = [
+ "bytes",
+ "encoding",
+ "thiserror",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
+
+[[package]]
+name = "js-sys"
+version = "0.3.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
+
+[[package]]
+name = "lock_api"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "matchers"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
+
+[[package]]
+name = "mio"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
+dependencies = [
+ "libc",
+ "log",
+ "miow",
+ "ntapi",
+ "winapi",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3b8c0d71734018084da0c0354193a5edfb81b20d2d57a92c5b154aefc554a4a"
+dependencies = [
+ "crypto-mac",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro-nested"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
+dependencies = [
+ "byteorder",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "rustls"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
+dependencies = [
+ "base64",
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "sasl"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "572d7dce028927bbc1cde21a93c6a1f4d7a1a7cc0e643d418a2864800a362dc9"
+dependencies = [
+ "base64",
+ "getrandom",
+ "hmac",
+ "pbkdf2",
+ "sha-1",
+ "sha2",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "sct"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "sedregex"
+version = "0.2.4"
+source = "git+https://gitlab.com/audron/sedregex#2ac3be5f56f53122cd89a160bc2ff9f7387a9467"
+dependencies = [
+ "regex",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16"
+dependencies = [
+ "block-buffer",
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sha2"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
+dependencies = [
+ "block-buffer",
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
+
+[[package]]
+name = "smallvec"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "subtle"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
+
+[[package]]
+name = "syn"
+version = "1.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "tokio"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "once_cell",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "tokio-macros",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
+dependencies = [
+ "rustls",
+ "tokio",
+ "webpki",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e177a5d8c3bf36de9ebe6d58537d8879e964332f93fb3339e43f618c81361af0"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940a12c99365c31ea8dd9ba04ec1be183ffe4920102bb7122c2f515437601e8e"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "tracing-futures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
+dependencies = [
+ "pin-project",
+ "tracing",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-serde"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
+dependencies = [
+ "serde",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa5553bf0883ba7c9cbe493b085c29926bd41b66afc31ff72cf17ff4fb60dcd5"
+dependencies = [
+ "ansi_term",
+ "chrono",
+ "lazy_static",
+ "matchers",
+ "regex",
+ "serde",
+ "serde_json",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-serde",
+]
+
+[[package]]
+name = "typenum"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
+
+[[package]]
+name = "web-sys"
+version = "0.3.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f"
+dependencies = [
+ "webpki",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..acce7b1
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "catinator"
+version = "0.1.0"
+authors = ["Max Audron <audron@cocaine.farm>"]
+edition = "2018"
+
+[dependencies]
+macros = { path = "./macros" }
+
+irc = { path = "/home/audron/repo/github.com/maxaudron/irc", version = "0.15", features = ["json", "tls-rust"], default_features = false }
+irc-proto = { path = "/home/audron/repo/github.com/maxaudron/irc/irc-proto" }
+sasl = "0.5"
+base64 = "0.13"
+
+serde = { version = "1.0", features = ["derive"] }
+toml = "0.5"
+
+anyhow = "1"
+
+futures = "0.3"
+tokio = { version = "1.5.0", features = ["full", "rt-multi-thread"] }
+
+tracing = "0.1"
+tracing-subscriber = "0.2"
+tracing-futures = "0.2"
+
+regex = "1"
+sedregex = { git = "https://gitlab.com/audron/sedregex" }
+
+[workspace]
+members = ["macros"]
diff --git a/macros/Cargo.lock b/macros/Cargo.lock
new file mode 100644
index 0000000..6d255d1
--- /dev/null
+++ b/macros/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "macros"
+version = "0.1.0"
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
new file mode 100644
index 0000000..30019d7
--- /dev/null
+++ b/macros/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "macros"
+version = "0.1.0"
+edition = "2018"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = { version = "1", features = ["parsing"] }
+proc-macro2 = "1"
+quote = "1"
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
new file mode 100644
index 0000000..222f14d
--- /dev/null
+++ b/macros/src/lib.rs
@@ -0,0 +1,158 @@
+extern crate proc_macro;
+use proc_macro::TokenStream;
+use proc_macro2::{Ident, Span};
+use quote::quote;
+
+use syn::parse_macro_input;
+
+mod macro_types;
+
+use macro_types::*;
+
+fn generate_help(items: &Items) -> proc_macro2::TokenStream {
+ let command_help = items.inner.iter().filter_map(|x| {
+ if let Item::Command(command) = x {
+ let help = command.help();
+ Some(quote! {
+ bot.send_notice(target, #help).unwrap();
+ })
+ } else {
+ None
+ }
+ });
+
+ let matcher_help = items.inner.iter().filter_map(|x| {
+ if let Item::Matcher(matcher) = x {
+ let help = matcher.help();
+ Some(quote! {
+ bot.send_notice(target, #help).unwrap();
+ })
+ } else {
+ None
+ }
+ });
+
+ let hook_help = items.inner.iter().filter_map(|x| {
+ if let Item::Hook(hook) = x {
+ let help = hook.help();
+ Some(quote! {
+ bot.send_notice(target, #help).unwrap();
+ })
+ } else {
+ None
+ }
+ });
+
+ let gen = quote! {
+ let target = message.source_nickname().unwrap();
+
+ bot.send_notice(target, "COMMANDS:").unwrap();
+ #(#command_help)*
+
+ bot.send_notice(target, "MATCHERS:").unwrap();
+ #(#matcher_help)*
+
+ bot.send_notice(target, "HOOKS:").unwrap();
+ #(#hook_help)*
+ };
+ gen.into()
+}
+
+#[proc_macro]
+pub fn catinator(tokens: TokenStream) -> TokenStream {
+ let items = parse_macro_input!(tokens as Items);
+
+ let hooks = items.inner.iter().filter_map(|x| {
+ if let Item::Hook(hook) = x {
+ Some(hook.to_call())
+ } else {
+ None
+ }
+ });
+
+ let commands = items.inner.iter().filter_map(|x| {
+ if let Item::Command(command) = x {
+ Some(command.to_call())
+ } else {
+ None
+ }
+ });
+
+ let matchers = items.inner.iter().filter_map(|x| {
+ if let Item::Matcher(command) = x {
+ Some(command.to_call())
+ } else {
+ None
+ }
+ });
+
+ let matchers_regex = items.inner.iter().filter_map(|x| {
+ if let Item::Matcher(matcher) = x {
+ let name = &matcher.name;
+ let regex = &matcher.matcher;
+
+ let ident = Ident::new(&name.value(), Span::call_site());
+
+ Some(quote! {
+ let #ident = regex::Regex::new(#regex).unwrap();
+ })
+ } else {
+ None
+ }
+ });
+
+ let help = generate_help(&items);
+
+ let gen = quote! {
+ use std::env;
+
+ use futures::prelude::*;
+ use tracing::{info, debug, trace};
+
+ use irc::client::prelude::*;
+ use catinator::Bot;
+
+ let config_path = env::var("CATINATOR_CONFIG").unwrap_or("config.toml".to_string());
+ info!("starting bot with config file {}", config_path);
+ let mut bot = Bot::new(&config_path).await.unwrap();
+
+ if bot.config.server.sasl {
+ info!("initializing sasl");
+ bot.sasl_init().await.unwrap()
+ }
+
+ #(#matchers_regex)*
+
+ info!("starting main event loop");
+ let mut stream = bot.irc_client.stream().unwrap();
+
+ while let Some(message) = stream.next().await.transpose().unwrap() {
+ trace!("{:?}", message);
+
+ let command = message.clone().command;
+
+ #(#hooks)*
+
+ match &command {
+ Command::PRIVMSG(_target, text) => {
+ let mut word = text.split_ascii_whitespace().next().unwrap().chars();
+ let prefix = word.next().unwrap();
+ let rest: String = word.collect();
+
+ if prefix == bot.config.settings.prefix {
+ if "help" == rest {
+ #help
+ }
+
+ #(#commands)*
+ } else {
+ #(#matchers)*
+ }
+ }
+ _ => (),
+ }
+ }
+ };
+
+ return gen.into();
+}
diff --git a/macros/src/macro_types/mod.rs b/macros/src/macro_types/mod.rs
new file mode 100644
index 0000000..262b3df
--- /dev/null
+++ b/macros/src/macro_types/mod.rs
@@ -0,0 +1,249 @@
+use proc_macro2::{Ident, Span};
+use quote::quote;
+
+use syn::{
+ parenthesized,
+ parse::{Parse, ParseStream},
+ Path,
+};
+use syn::{LitStr, Token};
+
+pub trait IrcItem {
+ fn to_call(&self) -> proc_macro2::TokenStream;
+ fn help(&self) -> String;
+}
+
+#[derive(Debug)]
+pub struct Items {
+ pub inner: Vec<Item>,
+}
+
+impl Parse for Items {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ let mut items: Vec<Item> = Vec::new();
+
+ while !input.is_empty() {
+ if input.peek(syn::Ident) && input.peek2(syn::token::Paren) {
+ items.push(input.parse()?)
+ } else {
+ return Err(input.error("line was not of expected format"));
+ }
+ }
+
+ Ok(Self { inner: items })
+ }
+}
+
+#[derive(Debug)]
+pub enum Item {
+ Command(Command),
+ Hook(Hook),
+ Matcher(Matcher),
+}
+
+impl Parse for Item {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ if input.peek(syn::Ident) {
+ let item: Ident = input.parse()?;
+ match item.to_string().as_str() {
+ "command" => input.parse().map(Item::Command),
+ "hook" => input.parse().map(Item::Hook),
+ "matcher" => input.parse().map(Item::Matcher),
+ _ => Err(input.error(format!(
+ "expected one of: command, hook or matcher not {}",
+ item.to_string()
+ ))),
+ }
+ } else {
+ Err(input.error("wrong type"))
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Command {
+ pub name: LitStr,
+ pub description: LitStr,
+ pub function: Path,
+}
+
+impl IrcItem for Command {
+ fn to_call(&self) -> proc_macro2::TokenStream {
+ let name = &self.name;
+ let function = &self.function;
+
+ quote! {
+ if #name == rest {
+ debug!(target: "command", "{} with {:?}", #name, message);
+ let result = #function(message.clone());
+ }
+ }
+ }
+
+ fn help(&self) -> String {
+ format!(" {}: {}", self.name.value(), self.description.value())
+ }
+}
+
+impl Parse for Command {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ let content;
+ parenthesized!(content in input);
+
+ let mut _token: Token![,];
+
+ if input.peek(Token![,]) {
+ _token = input.parse()?
+ }
+
+ let name = content.parse()?;
+ _token = content.parse()?;
+ let description = content.parse()?;
+ _token = content.parse()?;
+ let function = content.parse()?;
+
+ Ok(Self {
+ name,
+ description,
+ function,
+ })
+ }
+}
+
+#[derive(Debug)]
+pub struct Hook {
+ pub name: LitStr,
+ pub description: LitStr,
+ pub kind: Ident,
+ pub function: Path,
+}
+
+impl IrcItem for Hook {
+ fn to_call(&self) -> proc_macro2::TokenStream {
+ let name = &self.name;
+ let kind = &self.kind;
+ let kind_str = &self.kind.to_string();
+ let function = &self.function;
+
+ quote! {
+ if let Command::#kind(..) = &command {
+ debug!(target: "hook", "{} of kind {} with {:?}", #name, #kind_str, message);
+ let result = #function(&bot, message.clone());
+ }
+ }
+ }
+
+ fn help(&self) -> String {
+ format!(" {}: {}", self.name.value(), self.description.value())
+ }
+}
+
+impl Parse for Hook {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ let content;
+ parenthesized!(content in input);
+
+ let mut _token: Token![,];
+
+ if input.peek(Token![,]) {
+ _token = input.parse()?;
+ }
+
+ let name = content.parse()?;
+ _token = content.parse()?;
+ let description = content.parse()?;
+ _token = content.parse()?;
+ let kind: Ident = content.parse()?;
+ match kind.to_string().as_str() {
+ "PASS" | "NICK" | "USER" | "OPER" | "UserMODE" | "SERVICE" | "QUIT" | "SQUIT"
+ | "JOIN" | "PART" | "ChannelMODE" | "TOPIC" | "NAMES" | "LIST" | "INVITE" | "KICK"
+ | "PRIVMSG" | "NOTICE" | "MOTD" | "LUSERS" | "VERSION" | "STATS" | "LINKS" | "TIME"
+ | "CONNECT" | "TRACE" | "ADMIN" | "INFO" | "SERVLIST" | "SQUERY" | "WHO" | "WHOIS"
+ | "WHOWAS" | "KILL" | "PING" | "PONG" | "ERROR" | "AWAY" | "REHASH" | "DIE"
+ | "RESTART" | "SUMMON" | "USERS" | "WALLOPS" | "USERHOST" | "ISON" | "SAJOIN"
+ | "SAMODE" | "SANICK" | "SAPART" | "SAQUIT" | "NICKSERV" | "CHANSERV" | "OPERSERV"
+ | "BOTSERV" | "HOSTSERV" | "MEMOSERV" | "CAP" | "AUTHENTICATE" | "ACCOUNT"
+ | "METADATA" | "MONITOR" | "BATCH" | "CHGHOST" | "Response" | "Raw" => (),
+ _ => {
+ return Err(content.error(format!(
+ "expected one of: PASS, NICK, USER, OPER, UserMODE, SERVICE, QUIT, SQUIT
+ , JOIN, PART, ChannelMODE, TOPIC, NAMES, LIST, INVITE, KICK
+ , PRIVMSG, NOTICE, MOTD, LUSERS, VERSION, STATS, LINKS, TIME
+ , CONNECT, TRACE, ADMIN, INFO, SERVLIST, SQUERY, WHO, WHOIS
+ , WHOWAS, KILL, PING, PONG, ERROR, AWAY, REHASH, DIE
+ , RESTART, SUMMON, USERS, WALLOPS, USERHOST, ISON, SAJOIN
+ , SAMODE, SANICK, SAPART, SAQUIT, NICKSERV, CHANSERV, OPERSERV
+ , BOTSERV, HOSTSERV, MEMOSERV, CAP, AUTHENTICATE, ACCOUNT
+ , METADATA, MONITOR, BATCH, CHGHOST, Response, Raw not {}",
+ kind.to_string()
+ )))
+ }
+ }
+
+ _token = content.parse()?;
+ let function = content.parse()?;
+
+ Ok(Self {
+ name,
+ description,
+ kind,
+ function,
+ })
+ }
+}
+
+#[derive(Debug)]
+pub struct Matcher {
+ pub name: LitStr,
+ pub description: LitStr,
+ pub matcher: LitStr,
+ pub function: Path,
+}
+
+impl IrcItem for Matcher {
+ fn to_call(&self) -> proc_macro2::TokenStream {
+ let name = &self.name;
+ let function = &self.function;
+
+ let ident = Ident::new(&name.value(), Span::call_site());
+
+ quote! {
+ if #ident.is_match(text) {
+ debug!(target: "matcher", "{} with {:?}", #name, message);
+ let result = #function(&bot, message.clone());
+ }
+ }
+ }
+
+ fn help(&self) -> String {
+ format!(" {} ({}): {}", self.name.value(), self.matcher.value(), self.description.value())
+ }
+}
+
+impl Parse for Matcher {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ let content;
+ parenthesized!(content in input);
+
+ let mut _token: Token![,];
+
+ if input.peek(Token![,]) {
+ _token = input.parse()?;
+ }
+
+ let name = content.parse()?;
+ _token = content.parse()?;
+ let description = content.parse()?;
+ _token = content.parse()?;
+ let matcher = content.parse()?;
+ _token = content.parse()?;
+ let function = content.parse()?;
+
+ Ok(Self {
+ name,
+ description,
+ matcher,
+ function,
+ })
+ }
+}
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..32a9786
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+edition = "2018"
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..f7a0934
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,46 @@
+use serde::Deserialize;
+
+#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Deserialize)]
+pub struct Config {
+ pub user: User,
+ pub server: Server,
+ pub settings: Settings,
+}
+
+impl From<Config> for irc::client::prelude::Config {
+ fn from(input: Config) -> Self {
+ Self {
+ nickname: Some(input.user.nickname),
+ username: Some(input.user.username),
+ realname: Some(input.user.realname),
+ nick_password: Some(input.user.password),
+ server: Some(input.server.hostname),
+ port: Some(input.server.port),
+ use_tls: Some(input.server.tls),
+ channels: input.server.channels,
+ ..irc::client::prelude::Config::default()
+ }
+ }
+}
+
+#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Deserialize)]
+pub struct User {
+ pub nickname: String,
+ pub username: String,
+ pub password: String,
+ pub realname: String,
+}
+
+#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Deserialize)]
+pub struct Server {
+ pub hostname: String,
+ pub port: u16,
+ pub tls: bool,
+ pub sasl: bool,
+ pub channels: Vec<String>,
+}
+
+#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Deserialize)]
+pub struct Settings {
+ pub prefix: char,
+}
diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs
new file mode 100644
index 0000000..ba4a98d
--- /dev/null
+++ b/src/hooks/mod.rs
@@ -0,0 +1,39 @@
+use anyhow::Result;
+use irc::client::prelude::*;
+
+pub mod sed;
+pub mod shifty_eyes;
+
+pub use sed::*;
+pub use shifty_eyes::shifty_eyes;
+
+pub fn sasl(bot: &crate::Bot, msg: Message) -> Result<()> {
+ match msg.command {
+ Command::AUTHENTICATE(text) => {
+ use sasl::client::mechanisms::Plain;
+ use sasl::client::Mechanism;
+ use sasl::common::Credentials;
+
+ if text == "+" {
+ let creds = Credentials::default()
+ .with_username(bot.config.clone().user.username)
+ .with_password(bot.config.clone().user.password);
+
+ let mut mechanism = Plain::from_credentials(creds).unwrap();
+
+ let initial_data = mechanism.initial();
+
+ bot.irc_client.send_sasl(base64::encode(initial_data))?;
+ bot.irc_client.send(Command::CAP(
+ None,
+ irc_proto::command::CapSubCommand::END,
+ None,
+ None,
+ ))?;
+ }
+ }
+ _ => (),
+ }
+
+ Ok(())
+}
diff --git a/src/hooks/sed.rs b/src/hooks/sed.rs
new file mode 100644
index 0000000..095eb14
--- /dev/null
+++ b/src/hooks/sed.rs
@@ -0,0 +1,175 @@
+use anyhow::{anyhow, Result};
+use irc::client::prelude::*;
+
+use sedregex::ReplaceCommand;
+
+use std::cell::RefCell;
+
+static LOG_MAX_SIZE: usize = 10000;
+
+thread_local!(static LOG: RefCell<Vec::<(String, String)>> = RefCell::new(Vec::with_capacity(LOG_MAX_SIZE)));
+thread_local!(static RE: regex::Regex = regex::Regex::new(r"^s/").unwrap());
+
+pub fn log(_bot: &crate::Bot, msg: Message) -> Result<()> {
+ log_msg(msg)
+}
+
+fn log_msg(msg: Message) -> Result<()> {
+ if let Command::PRIVMSG(_, text) = msg.command.clone() {
+ LOG.with(|log_cell| {
+ let mut log = log_cell.borrow_mut();
+ if log.len() >= LOG_MAX_SIZE {
+ let _ = log.pop();
+ }
+ log.push((msg.source_nickname().unwrap().to_string(), text))
+ });
+ }
+ Ok(())
+}
+
+pub fn replace(bot: &crate::Bot, msg: Message) -> Result<()> {
+ match find_and_replace(&msg) {
+ Ok(res) => {
+ bot.send_privmsg(msg.response_target().unwrap(), res.as_str())
+ .unwrap();
+ Ok(())
+ }
+ Err(_) => Ok(()),
+ }
+}
+
+fn find_and_replace(msg: &Message) -> Result<String> {
+ if let Command::PRIVMSG(_, text) = msg.command.clone() {
+ let cmd = match ReplaceCommand::new(text.as_str()) {
+ Ok(cmd) => cmd,
+ Err(_) => return Err(anyhow!("building replace command failed")),
+ };
+
+ return LOG.with(|log_cell| {
+ log_cell
+ .borrow()
+ .iter()
+ .rev()
+ .find(|(_, text)| cmd.expr.is_match(text) && !RE.with(|re| re.is_match(text)))
+ .and_then(|(nick, text)| Some(format!("<{}> {}", nick, cmd.execute(text))))
+ .map_or(Err(anyhow!("replace failed")), |v| Ok(v))
+ });
+ }
+
+ Err(anyhow!("not a privmsg"))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use test::Bencher;
+
+ fn populate_log() {
+ LOG.with(|log_cell| {
+ let mut log = log_cell.borrow_mut();
+ log.push((
+ "user".to_string(),
+ "this is a long message which will be replaced".to_string(),
+ ));
+ for _ in 0..LOG_MAX_SIZE-1 {
+ log.push((
+ "user".to_string(),
+ "this is a long message which doesn't matter".to_string(),
+ ))
+ }
+ });
+ }
+
+ #[test]
+ fn test_log_limit() {
+ populate_log();
+
+ LOG.with(|log_cell| {
+ let log = log_cell.borrow();
+ assert_eq!(log.len(), LOG_MAX_SIZE)
+ });
+
+ log_msg(Message {
+ tags: None,
+ prefix: Some(Prefix::Nickname(
+ "user".to_string(),
+ "username".to_string(),
+ "userhost".to_string(),
+ )),
+ command: Command::PRIVMSG(
+ "#channel".to_string(),
+ "this is the 10001th message".to_string(),
+ ),
+ })
+ .unwrap();
+
+ LOG.with(|log_cell| {
+ let log = log_cell.borrow();
+ assert_eq!(log.len(), LOG_MAX_SIZE)
+ });
+ }
+
+ #[test]
+ fn test_replace() {
+ populate_log();
+ assert_eq!(
+ find_and_replace(&Message {
+ tags: None,
+ prefix: None,
+ command: Command::PRIVMSG(
+ "#channel".to_string(),
+ "s/will be/has been/".to_string(),
+ ),
+ })
+ .unwrap(),
+ "<user> this is a long message which has been replaced"
+ )
+ }
+
+ #[test]
+ fn test_replace_complex() {
+ populate_log();
+ assert_eq!(
+ find_and_replace(&Message {
+ tags: None,
+ prefix: None,
+ command: Command::PRIVMSG(
+ "#channel".to_string(),
+ "s/(will).*(be)/$2 $1/".to_string(),
+ ),
+ })
+ .unwrap(),
+ "<user> this is a long message which be will replaced"
+ )
+ }
+
+ #[bench]
+ fn bench_replace(b: &mut Bencher) {
+ populate_log();
+ b.iter(|| {
+ find_and_replace(&Message {
+ tags: None,
+ prefix: None,
+ command: Command::PRIVMSG(
+ "#channel".to_string(),
+ "s/will be/has been/".to_string(),
+ ),
+ })
+ });
+ }
+
+ #[bench]
+ fn bench_replace_complex(b: &mut Bencher) {
+ populate_log();
+ b.iter(|| {
+ find_and_replace(&Message {
+ tags: None,
+ prefix: None,
+ command: Command::PRIVMSG(
+ "#channel".to_string(),
+ "s/will be/has been/".to_string(),
+ ),
+ })
+ });
+ }
+}
diff --git a/src/hooks/shifty_eyes.rs b/src/hooks/shifty_eyes.rs
new file mode 100644
index 0000000..beb3a9e
--- /dev/null
+++ b/src/hooks/shifty_eyes.rs
@@ -0,0 +1,46 @@
+use anyhow::{anyhow, Result};
+use irc::client::prelude::*;
+
+const EYES: [char; 10] = ['^', 'v', 'V', '>', '<', 'x', 'X', '-', 'o', 'O'];
+const NOSE: [char; 7] = ['.', '_', '-', ';', '\'', '"', '~'];
+
+pub fn shifty_eyes(bot: &crate::Bot, msg: Message) -> Result<()> {
+ if let Command::PRIVMSG(_, text) = msg.command.clone() {
+ if text.len() == 3 {
+ let mut chars = text.chars();
+ let mut left = chars.next().unwrap();
+ let middle = chars.next().unwrap();
+ let mut right = chars.next().unwrap();
+
+ if EYES.contains(&left) && NOSE.contains(&middle) && EYES.contains(&right) {
+ left = invert(left)?;
+ right = invert(right)?;
+
+ let mut result = String::new();
+ result.push(left);
+ result.push(middle);
+ result.push(right);
+
+ bot.send_privmsg(msg.response_target().unwrap(), result.as_str())?;
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn invert(input: char) -> Result<char> {
+ match input {
+ '^' => Ok('v'),
+ 'v' => Ok('^'),
+ 'V' => Ok('^'),
+ '>' => Ok('<'),
+ '<' => Ok('>'),
+ 'x' => Ok('o'),
+ 'X' => Ok('O'),
+ '-' => Ok('o'),
+ 'o' => Ok('-'),
+ 'O' => Ok('-'),
+ _ => Err(anyhow!("not a valid char")),
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..1e3be91
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,70 @@
+#![feature(test)]
+extern crate test;
+
+use anyhow::Result;
+
+use irc::client::prelude::*;
+
+pub mod config;
+pub mod hooks;
+
+pub use macros::catinator;
+
+#[macro_export]
+macro_rules! reply {
+ ( $msg:expr, $text:expr ) => {
+ bot.send_privmsg($msg.response_target().unwrap(), $text.as_str())?;
+ };
+}
+
+
+pub struct Bot {
+ pub config: config::Config,
+ pub irc_client: irc::client::Client,
+}
+
+impl Bot {
+ pub async fn new(config_path: &str) -> Result<Bot> {
+ use std::fs;
+
+ let config_str = fs::read_to_string(config_path)?;
+ let config: config::Config = toml::from_str(&config_str)?;
+
+ let irc_config: Config = config.clone().into();
+
+ let irc_client = Client::from_config(irc_config.clone()).await?;
+
+ Ok(Bot { irc_client, config })
+ }
+
+ pub async fn sasl_init(&self) -> Result<()> {
+ self.irc_client
+ .send_cap_req(&vec![irc::client::prelude::Capability::Sasl])?;
+ self.irc_client
+ .send(Command::NICK(self.config.user.nickname.clone()))?;
+ self.irc_client.send(Command::USER(
+ self.config.user.nickname.clone(),
+ "0".to_owned(),
+ self.config.user.realname.clone(),
+ ))?;
+ self.irc_client.send_sasl_plain()?;
+
+ Ok(())
+ }
+
+ pub fn send_privmsg(
+ &self,
+ target: &str,
+ message: &str,
+ ) -> std::result::Result<(), irc::error::Error> {
+ self.irc_client.send_privmsg(target, message)
+ }
+
+ pub fn send_notice(
+ &self,
+ target: &str,
+ message: &str,
+ ) -> std::result::Result<(), irc::error::Error> {
+ self.irc_client.send_notice(target, message)
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..10082c3
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,38 @@
+#[tokio::main]
+async fn main() {
+ use catinator::catinator;
+
+ tracing_subscriber::fmt()
+ .compact()
+ .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL)
+ .with_max_level(tracing::Level::DEBUG)
+ .with_thread_ids(true)
+ .init();
+
+ catinator!(
+ hook(
+ "sasl",
+ "Handle Authentication.",
+ AUTHENTICATE,
+ catinator::hooks::sasl
+ ),
+ hook(
+ "sed_log",
+ "Log messages for use with sed replace, max 10k lines.",
+ PRIVMSG,
+ catinator::hooks::sed::log
+ ),
+ matcher(
+ "shifty_eyes",
+ ">.>",
+ r"^\S{3}$",
+ catinator::hooks::shifty_eyes
+ ),
+ matcher(
+ "replace",
+ "sed style replace with regex support. i/g/U/x sed flags available",
+ r"^s/",
+ catinator::hooks::sed::replace
+ ),
+ );
+}