├── .gitignore ├── example_config.toml ├── Cargo.toml ├── .github └── workflows │ └── ci.yml ├── README.md ├── LICENSE ├── Cargo.lock └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | 13 | # IntelliJ 14 | /.idea 15 | *.iml 16 | 17 | # Config 18 | config.toml 19 | -------------------------------------------------------------------------------- /example_config.toml: -------------------------------------------------------------------------------- 1 | irc_nick = "nickname" 2 | irc_server = "irc.example.org" 3 | irc_port = 80 # optional, defaults to 6667 or 6697 with irc_ssl enabled 4 | irc_ssl = false # optional, defaults to true 5 | irc_password = "password" # optional 6 | slack_user = "your slack username (not the bot's)" 7 | slack_token = "xxxx-xxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx" 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "slack_irc_rust" 3 | version = "0.2.3" 4 | authors = ["Erik "] 5 | 6 | [dependencies] 7 | futures = "0.1" 8 | irc = "0.11" 9 | hyper = "0.9" 10 | slack = "0.16" 11 | lazy_static = "0.2" 12 | regex = "0.1" 13 | toml = "0.2" 14 | rustc-serialize = "0.3" 15 | log = "0.3" 16 | multimap = "0.3" 17 | time = "0.1" 18 | 19 | [profile.release] 20 | panic = "abort" 21 | lto = true 22 | codegen-units = 1 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v*.*.* 9 | pull_request: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v1 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: stable 19 | - run: cargo build --release 20 | - run: strip target/release/slack_irc_rust 21 | - run: ls -lh target/release/slack_irc_rust 22 | - uses: softprops/action-gh-release@v1 23 | if: startsWith(github.ref, 'refs/tags/') 24 | with: 25 | files: target/release/slack_irc_rust 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # slack-irc-rust 2 | 3 | Rust port of [erikdesjardins/slack-irc](https://github.com/erikdesjardins/slack-irc), which was forked from [ekmartin/slack-irc](https://github.com/ekmartin/slack-irc) (and modified for a single user). 4 | 5 | ## Usage 6 | 7 | - Linux binaries are available on the [releases page](https://github.com/erikdesjardins/slack-irc-rust/releases); for everything else, build from source. 8 | - Run with `./slack_irc_rust path/to/config.toml` (defaults to `config.toml` in the CWD) 9 | - see `example_config.toml` 10 | 11 | ## Features 12 | 13 | - Send raw commands by prefixing your message with `%` (e.g. `%PRIVMSG #test testing` in any channel) 14 | - Send "/msg" messages by IMing the bot with their nick prefixed (e.g. `someones_nick: hi` in the IM channel) 15 | - Receive "/msg" messages in the same IM channel 16 | - Invite the bot to a Slack channel to join the IRC channel of the same name (and kick the bot to part) 17 | - Native slack `/me` messages work in IRC 18 | - Slack active/away status sent to IRC 19 | - Slack channel topic synced to/from IRC 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Erik Desjardins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aho-corasick" 3 | version = "0.5.3" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "bitflags" 11 | version = "0.7.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "bitflags" 16 | version = "1.0.3" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | 19 | [[package]] 20 | name = "byteorder" 21 | version = "0.5.3" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | 24 | [[package]] 25 | name = "cfg-if" 26 | version = "0.1.3" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "cookie" 31 | version = "0.2.5" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | dependencies = [ 34 | "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 37 | "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 38 | ] 39 | 40 | [[package]] 41 | name = "encoding" 42 | version = "0.2.33" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | dependencies = [ 45 | "encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 47 | "encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 48 | "encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 49 | "encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", 50 | ] 51 | 52 | [[package]] 53 | name = "encoding-index-japanese" 54 | version = "1.20141219.5" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | dependencies = [ 57 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 58 | ] 59 | 60 | [[package]] 61 | name = "encoding-index-korean" 62 | version = "1.20141219.5" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | dependencies = [ 65 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 66 | ] 67 | 68 | [[package]] 69 | name = "encoding-index-simpchinese" 70 | version = "1.20141219.5" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | dependencies = [ 73 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 74 | ] 75 | 76 | [[package]] 77 | name = "encoding-index-singlebyte" 78 | version = "1.20141219.5" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | dependencies = [ 81 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 82 | ] 83 | 84 | [[package]] 85 | name = "encoding-index-tradchinese" 86 | version = "1.20141219.5" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | dependencies = [ 89 | "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 90 | ] 91 | 92 | [[package]] 93 | name = "encoding_index_tests" 94 | version = "0.1.4" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | 97 | [[package]] 98 | name = "fuchsia-zircon" 99 | version = "0.3.3" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | dependencies = [ 102 | "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 104 | ] 105 | 106 | [[package]] 107 | name = "fuchsia-zircon-sys" 108 | version = "0.3.3" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | 111 | [[package]] 112 | name = "futures" 113 | version = "0.1.21" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | 116 | [[package]] 117 | name = "gcc" 118 | version = "0.3.54" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | 121 | [[package]] 122 | name = "gdi32-sys" 123 | version = "0.2.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | dependencies = [ 126 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 128 | ] 129 | 130 | [[package]] 131 | name = "hpack" 132 | version = "0.2.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | dependencies = [ 135 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 136 | ] 137 | 138 | [[package]] 139 | name = "httparse" 140 | version = "1.2.4" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | 143 | [[package]] 144 | name = "hyper" 145 | version = "0.9.18" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | dependencies = [ 148 | "cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 149 | "httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 150 | "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 151 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 152 | "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 154 | "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", 155 | "openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 156 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 157 | "solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 158 | "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 159 | "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 160 | "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 161 | "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 163 | ] 164 | 165 | [[package]] 166 | name = "idna" 167 | version = "0.1.4" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | dependencies = [ 170 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 171 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 172 | "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 173 | ] 174 | 175 | [[package]] 176 | name = "irc" 177 | version = "0.11.8" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | dependencies = [ 180 | "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 181 | "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 183 | "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 184 | ] 185 | 186 | [[package]] 187 | name = "kernel32-sys" 188 | version = "0.2.2" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | dependencies = [ 191 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 192 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 193 | ] 194 | 195 | [[package]] 196 | name = "language-tags" 197 | version = "0.2.2" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | 200 | [[package]] 201 | name = "lazy_static" 202 | version = "0.2.11" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | 205 | [[package]] 206 | name = "libc" 207 | version = "0.2.42" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | 210 | [[package]] 211 | name = "libressl-pnacl-sys" 212 | version = "2.1.6" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | dependencies = [ 215 | "pnacl-build-helper 1.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 216 | ] 217 | 218 | [[package]] 219 | name = "log" 220 | version = "0.3.9" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | dependencies = [ 223 | "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 224 | ] 225 | 226 | [[package]] 227 | name = "log" 228 | version = "0.4.2" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | dependencies = [ 231 | "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 232 | ] 233 | 234 | [[package]] 235 | name = "matches" 236 | version = "0.1.6" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | 239 | [[package]] 240 | name = "memchr" 241 | version = "0.1.11" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | dependencies = [ 244 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 245 | ] 246 | 247 | [[package]] 248 | name = "mime" 249 | version = "0.2.6" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | dependencies = [ 252 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 253 | ] 254 | 255 | [[package]] 256 | name = "multimap" 257 | version = "0.3.0" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | 260 | [[package]] 261 | name = "net2" 262 | version = "0.2.32" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | dependencies = [ 265 | "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 266 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 268 | ] 269 | 270 | [[package]] 271 | name = "num_cpus" 272 | version = "1.8.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | dependencies = [ 275 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 276 | ] 277 | 278 | [[package]] 279 | name = "openssl" 280 | version = "0.7.14" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | dependencies = [ 283 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 284 | "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", 285 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 286 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)", 288 | "openssl-sys-extras 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", 289 | ] 290 | 291 | [[package]] 292 | name = "openssl-sys" 293 | version = "0.7.17" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | dependencies = [ 296 | "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 297 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 298 | "libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 299 | "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", 300 | "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 301 | ] 302 | 303 | [[package]] 304 | name = "openssl-sys-extras" 305 | version = "0.7.14" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | dependencies = [ 308 | "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", 309 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 310 | "openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)", 311 | ] 312 | 313 | [[package]] 314 | name = "openssl-verify" 315 | version = "0.1.0" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | dependencies = [ 318 | "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", 319 | ] 320 | 321 | [[package]] 322 | name = "percent-encoding" 323 | version = "1.0.1" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | 326 | [[package]] 327 | name = "pkg-config" 328 | version = "0.3.11" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | 331 | [[package]] 332 | name = "pnacl-build-helper" 333 | version = "1.4.11" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | dependencies = [ 336 | "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 337 | "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 338 | ] 339 | 340 | [[package]] 341 | name = "rand" 342 | version = "0.3.22" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | dependencies = [ 345 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 346 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 347 | "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 348 | ] 349 | 350 | [[package]] 351 | name = "rand" 352 | version = "0.4.2" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | dependencies = [ 355 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 356 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 357 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 358 | ] 359 | 360 | [[package]] 361 | name = "redox_syscall" 362 | version = "0.1.40" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | 365 | [[package]] 366 | name = "regex" 367 | version = "0.1.80" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | dependencies = [ 370 | "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 371 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 372 | "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 373 | "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 374 | "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 375 | ] 376 | 377 | [[package]] 378 | name = "regex-syntax" 379 | version = "0.3.9" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | 382 | [[package]] 383 | name = "remove_dir_all" 384 | version = "0.5.1" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | dependencies = [ 387 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 388 | ] 389 | 390 | [[package]] 391 | name = "rustc-serialize" 392 | version = "0.3.24" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | 395 | [[package]] 396 | name = "same-file" 397 | version = "0.1.3" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | dependencies = [ 400 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 401 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 402 | ] 403 | 404 | [[package]] 405 | name = "slack" 406 | version = "0.16.0" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | dependencies = [ 409 | "hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)", 410 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 411 | "slack_api 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", 412 | "websocket 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)", 413 | ] 414 | 415 | [[package]] 416 | name = "slack_api" 417 | version = "0.15.0" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | dependencies = [ 420 | "hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)", 421 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 422 | ] 423 | 424 | [[package]] 425 | name = "slack_irc_rust" 426 | version = "0.2.3" 427 | dependencies = [ 428 | "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", 429 | "hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)", 430 | "irc 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)", 431 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 432 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 433 | "multimap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 434 | "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", 435 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 436 | "slack 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", 437 | "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 438 | "toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 439 | ] 440 | 441 | [[package]] 442 | name = "solicit" 443 | version = "0.4.4" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | dependencies = [ 446 | "hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 447 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 448 | ] 449 | 450 | [[package]] 451 | name = "tempdir" 452 | version = "0.3.7" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | dependencies = [ 455 | "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 456 | "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 457 | ] 458 | 459 | [[package]] 460 | name = "thread-id" 461 | version = "2.0.0" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | dependencies = [ 464 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 465 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 466 | ] 467 | 468 | [[package]] 469 | name = "thread_local" 470 | version = "0.2.7" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | dependencies = [ 473 | "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 474 | ] 475 | 476 | [[package]] 477 | name = "time" 478 | version = "0.1.40" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | dependencies = [ 481 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 482 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 483 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 484 | ] 485 | 486 | [[package]] 487 | name = "toml" 488 | version = "0.2.1" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | dependencies = [ 491 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 492 | ] 493 | 494 | [[package]] 495 | name = "traitobject" 496 | version = "0.0.1" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | 499 | [[package]] 500 | name = "typeable" 501 | version = "0.1.2" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | 504 | [[package]] 505 | name = "unicase" 506 | version = "1.4.2" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | dependencies = [ 509 | "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 510 | ] 511 | 512 | [[package]] 513 | name = "unicode-bidi" 514 | version = "0.3.4" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | dependencies = [ 517 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 518 | ] 519 | 520 | [[package]] 521 | name = "unicode-normalization" 522 | version = "0.1.7" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | 525 | [[package]] 526 | name = "url" 527 | version = "1.7.0" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | dependencies = [ 530 | "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 531 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 532 | "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 533 | ] 534 | 535 | [[package]] 536 | name = "user32-sys" 537 | version = "0.2.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | dependencies = [ 540 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 541 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 542 | ] 543 | 544 | [[package]] 545 | name = "utf8-ranges" 546 | version = "0.1.3" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | 549 | [[package]] 550 | name = "version_check" 551 | version = "0.1.3" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | 554 | [[package]] 555 | name = "walkdir" 556 | version = "1.0.7" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | dependencies = [ 559 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 560 | "same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 561 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 562 | ] 563 | 564 | [[package]] 565 | name = "websocket" 566 | version = "0.17.1" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | dependencies = [ 569 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 570 | "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 571 | "hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)", 572 | "net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", 573 | "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", 574 | "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", 575 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 576 | "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 577 | "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 578 | ] 579 | 580 | [[package]] 581 | name = "winapi" 582 | version = "0.2.8" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | 585 | [[package]] 586 | name = "winapi" 587 | version = "0.3.5" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | dependencies = [ 590 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 591 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 592 | ] 593 | 594 | [[package]] 595 | name = "winapi-build" 596 | version = "0.1.1" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | 599 | [[package]] 600 | name = "winapi-i686-pc-windows-gnu" 601 | version = "0.4.0" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | 604 | [[package]] 605 | name = "winapi-x86_64-pc-windows-gnu" 606 | version = "0.4.0" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | 609 | [metadata] 610 | "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" 611 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 612 | "checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" 613 | "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" 614 | "checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" 615 | "checksum cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0e3d6405328b6edb412158b3b7710e2634e23f3614b9bb1c412df7952489a626" 616 | "checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" 617 | "checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" 618 | "checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" 619 | "checksum encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" 620 | "checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" 621 | "checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" 622 | "checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" 623 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 624 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 625 | "checksum futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "1a70b146671de62ec8c8ed572219ca5d594d9b06c0b364d5e67b722fc559b48c" 626 | "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" 627 | "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" 628 | "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58" 629 | "checksum httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f407128745b78abc95c0ffbe4e5d37427fdc0d45470710cfef8c44522a2e37" 630 | "checksum hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9bf64f730d6ee4b0528a5f0a316363da9d8104318731509d4ccc86248f82b3" 631 | "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" 632 | "checksum irc 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6a45f7136bbfeec4377afc6363b38440ce153d8a61777d56da0c6b1176cf135a" 633 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 634 | "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 635 | "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 636 | "checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" 637 | "checksum libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "cbc058951ab6a3ef35ca16462d7642c4867e6403520811f28537a4e2f2db3e71" 638 | "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 639 | "checksum log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6fddaa003a65722a7fb9e26b0ce95921fe4ba590542ced664d8ce2fa26f9f3ac" 640 | "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" 641 | "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" 642 | "checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" 643 | "checksum multimap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9223f4774d08e06185e44e555b9a7561243d387bac49c78a6205c42d6975fbf2" 644 | "checksum net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "9044faf1413a1057267be51b5afba8eb1090bd2231c693664aa1db716fe1eae0" 645 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 646 | "checksum openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "c4117b6244aac42ed0150a6019b4d953d28247c5dd6ae6f46ae469b5f2318733" 647 | "checksum openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)" = "89c47ee94c352eea9ddaf8e364be7f978a3bb6d66d73176572484238dd5a5c3f" 648 | "checksum openssl-sys-extras 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "11c5e1dba7d3d03d80f045bf0d60111dc69213b67651e7c889527a3badabb9fa" 649 | "checksum openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed86cce894f6b0ed4572e21eb34026f1dc8869cb9ee3869029131bc8c3feb2d" 650 | "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 651 | "checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f" 652 | "checksum pnacl-build-helper 1.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dfbe13ee77c06fb633d71c72438bd983286bb3521863a753ade8e951c7efb090" 653 | "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" 654 | "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" 655 | "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" 656 | "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" 657 | "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" 658 | "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" 659 | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 660 | "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" 661 | "checksum slack 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "331272885a9a42a06cab3b3519075a3a31c8826564845b9ac12ed536aeda6a1e" 662 | "checksum slack_api 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "78b3645da87e45b90c164935a7fc6032c96585861a553f566263294b2867e7ef" 663 | "checksum solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "172382bac9424588d7840732b250faeeef88942e37b6e35317dce98cafdd75b2" 664 | "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" 665 | "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" 666 | "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" 667 | "checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" 668 | "checksum toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" 669 | "checksum traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "07eaeb7689bb7fca7ce15628319635758eda769fed481ecfe6686ddef2600616" 670 | "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" 671 | "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" 672 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 673 | "checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" 674 | "checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7" 675 | "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" 676 | "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" 677 | "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" 678 | "checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" 679 | "checksum websocket 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a1a6ea5ed0367f32eb3d94dcc58859ef4294b5f75ba983dbf56ac314af45d" 680 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 681 | "checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" 682 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 683 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 684 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 685 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate futures; 2 | #[macro_use] 3 | extern crate log; 4 | extern crate irc; 5 | extern crate hyper; 6 | extern crate slack; 7 | #[macro_use] 8 | extern crate lazy_static; 9 | extern crate regex; 10 | extern crate toml; 11 | extern crate rustc_serialize; 12 | extern crate multimap; 13 | extern crate time; 14 | 15 | use std::env; 16 | use std::fmt::Debug; 17 | use std::collections::HashMap; 18 | use std::thread; 19 | use std::sync::{Arc, Mutex}; 20 | use std::sync::mpsc::{Sender, channel}; 21 | use std::default::Default; 22 | use std::fs::File; 23 | use std::io::prelude::*; 24 | use std::time::Duration; 25 | 26 | use futures::sync::oneshot; 27 | use futures::*; 28 | use log::{LogRecord, LogMetadata, LogLevelFilter}; 29 | use irc::client::prelude::{Command, Response, IrcServer, Server, ServerExt}; 30 | use regex::Regex; 31 | use rustc_serialize::json::Json; 32 | use multimap::MultiMap; 33 | use time::Tm; 34 | 35 | struct StdoutLogger; 36 | 37 | impl log::Log for StdoutLogger { 38 | fn enabled(&self, _: &LogMetadata) -> bool { 39 | true 40 | } 41 | 42 | fn log(&self, record: &LogRecord) { 43 | println!("[{}] [{}] {}", time::now().strftime("%Y-%m-%d %T").unwrap(), record.level(), record.args()); 44 | } 45 | } 46 | 47 | #[derive(Debug, RustcDecodable)] 48 | struct Config { 49 | irc_nick: String, 50 | irc_server: String, 51 | irc_port: Option, 52 | irc_ssl: Option, 53 | irc_password: Option, 54 | slack_user: String, 55 | slack_token: String, 56 | } 57 | 58 | #[derive(Debug)] 59 | struct IrcInit { 60 | channels: Vec, 61 | } 62 | 63 | #[derive(Debug)] 64 | enum ToIrc { 65 | Message { to: String, msg: String }, 66 | MeMessage { to: String, msg: String }, 67 | Raw(String), 68 | Away(bool), 69 | Join(String), 70 | Part(String), 71 | Topic { chan: String, topic: String }, 72 | } 73 | 74 | #[derive(Debug)] 75 | enum ToSlack { 76 | Message { to: String, from: String, msg: String }, 77 | Topic { by: Option, chan: String, topic: Option }, 78 | Kick { by: String, chans: Vec, nicks: Vec, reason: Option }, 79 | Join { nick: String, chans: Vec }, 80 | Part { nick: String, chans: Vec, reason: Option }, 81 | Quit { nick: String, chans: Vec, reason: Option }, 82 | Nick { old_nick: String, new_nick: String, chans: Vec }, 83 | Mode { by: String, name: String, modes: String, params: Option }, 84 | Whois(String, Whois), 85 | Error(String), 86 | UpdateChannels, 87 | } 88 | 89 | #[derive(Debug)] 90 | enum Whois { 91 | User { user: String, host: String, real_name: String }, 92 | Host(String), 93 | Channels(Vec), 94 | Server { server: String, server_info: String }, 95 | Modes(String), 96 | Away(String), 97 | Account { msg: String, account: String }, 98 | Registered(String), 99 | Secure(String), 100 | Idle { seconds: u32, signon: Tm }, 101 | End(String), 102 | } 103 | 104 | struct SlackHandler<'a> { 105 | irc_tx: &'a Sender, 106 | slack_tx: &'a Sender, 107 | user_id: &'a str, 108 | bot_channel: &'a str, 109 | } 110 | 111 | fn get_channel_with_id(cli: &slack::RtmClient, id: &str) -> Option { 112 | cli.get_channels().into_iter().find(|channel| channel.id == id) 113 | } 114 | 115 | fn get_user_with_id(cli: &slack::RtmClient, id: &str) -> Option { 116 | cli.get_users().into_iter().find(|user| user.id == id) 117 | } 118 | 119 | fn log_err(res: Result) { 120 | if let Err(err) = res { 121 | error!("{:?}", err); 122 | } 123 | } 124 | 125 | fn parse_slack_text(text: &str, cli: &slack::RtmClient) -> String { 126 | lazy_static! { 127 | static ref REPLACEMENTS: Vec<(Regex, &'static str)> = vec![ 128 | (Regex::new(r"\n|\r\n|\r").unwrap(), " "), 129 | (Regex::new(r"&").unwrap(), "&"), 130 | (Regex::new(r"<").unwrap(), "<"), 131 | (Regex::new(r">").unwrap(), ">"), 132 | (Regex::new(r"").unwrap(), "@channel"), 133 | (Regex::new(r"").unwrap(), "@group"), 134 | (Regex::new(r"").unwrap(), "@everyone"), 135 | // http://google.com|google.com -> google.com 136 | (Regex::new(r"\bhttps?://\S+\|(\S+)").unwrap(), "$1"), 137 | // mailto:someone@example.com|someone@example.com -> someone@example.com 138 | (Regex::new(r"\bmailto:\S+\|(\S+)").unwrap(), "$1"), 139 | ]; 140 | static ref EMOJIS: HashMap<&'static str, &'static str> = { 141 | let mut m = HashMap::new(); 142 | m.insert("smile", ":)"); 143 | m.insert("simple_smile", ":)"); 144 | m.insert("smiley", ":-)"); 145 | m.insert("grin", ":D"); 146 | m.insert("wink", ";)"); 147 | m.insert("smirk", ";)"); 148 | m.insert("blush", ":$"); 149 | m.insert("stuck_out_tongue", ":P"); 150 | m.insert("stuck_out_tongue_winking_eye", ";P"); 151 | m.insert("stuck_out_tongue_closed_eyes", "xP"); 152 | m.insert("disappointed", ":("); 153 | m.insert("astonished", ":O"); 154 | m.insert("open_mouth", ":O"); 155 | m.insert("heart", "<3"); 156 | m.insert("broken_heart", ":("); 159 | m.insert("cry", ":,("); 160 | m.insert("frowning", ":("); 161 | m.insert("imp", "]:("); 162 | m.insert("innocent", "o:)"); 163 | m.insert("joy", ":,)"); 164 | m.insert("kissing", ":*"); 165 | m.insert("laughing", "x)"); 166 | m.insert("neutral_face", ":|"); 167 | m.insert("no_mouth", ":-"); 168 | m.insert("rage", ":@"); 169 | m.insert("smiling_imp", "]:)"); 170 | m.insert("sob", ":,'("); 171 | m.insert("sunglasses", "8)"); 172 | m.insert("sweat", ",:("); 173 | m.insert("sweat_smile", ",:)"); 174 | m.insert("unamused", ":$"); 175 | m 176 | }; 177 | static ref FUNCTIONS: Vec<(Regex, Box String + Sync>)> = vec![ 178 | (Regex::new(r"<#(C\w+)\|?([\w-]+)?>").unwrap(), Box::new(|captures: ®ex::Captures, cli: &slack::RtmClient| { 179 | if let Some(id) = captures.at(1) { 180 | if let Some(channel) = get_channel_with_id(cli, id) { 181 | return format!("#{}", channel.name).into() 182 | } 183 | } 184 | captures.at(2).unwrap_or("").into() 185 | })), 186 | (Regex::new(r"<@(U\w+)\|?([\w-]+)?>").unwrap(), Box::new(|captures: ®ex::Captures, cli: &slack::RtmClient| { 187 | if let Some(id) = captures.at(1) { 188 | if let Some(user) = get_user_with_id(cli, id) { 189 | return format!("@{}", user.name).into() 190 | } 191 | } 192 | captures.at(2).unwrap_or("").into() 193 | })), 194 | (Regex::new(r"<([^!]\S+)>").unwrap(), Box::new(|captures: ®ex::Captures, _| { 195 | captures.at(1).unwrap_or("").into() 196 | })), 197 | (Regex::new(r"").unwrap(), Box::new(|captures: ®ex::Captures, _| { 198 | format!("<{}>", captures.at(2).or(captures.at(1)).unwrap_or("")).into() 199 | })), 200 | (Regex::new(r":(\w+):").unwrap(), Box::new(|captures: ®ex::Captures, _| { 201 | if let Some(&emoji) = EMOJIS.get(captures.at(1).unwrap()) { 202 | emoji.into() 203 | } else { 204 | captures.at(0).unwrap().into() 205 | } 206 | })), 207 | ]; 208 | } 209 | 210 | let text = REPLACEMENTS.iter().fold(text.into(), |text: String, &(ref re, replacement)| { 211 | re.replace_all(&text, replacement) 212 | }); 213 | 214 | FUNCTIONS.iter().fold(text, |text, &(ref re, ref replacement)| { 215 | re.replace_all(&text, |captures: ®ex::Captures| { 216 | replacement(captures, cli) 217 | }) 218 | }) 219 | } 220 | 221 | fn parse_irc_text(text: &str) -> String { 222 | lazy_static! { 223 | static ref REPLACEMENTS: Vec<(Regex, &'static str)> = vec![ 224 | // strip formatting; via irc-colors.js 225 | // stripStyle str.replace(/([\x0F\x02\x16\x1F])(.+)\1/g, '$2') 226 | (Regex::new(r"\x0F(.+)\x0F").unwrap(), "$1"), 227 | (Regex::new(r"\x02(.+)\x02").unwrap(), "$1"), // bold 228 | (Regex::new(r"\x16(.+)\x16").unwrap(), "$1"), 229 | (Regex::new(r"\x1D(.+)\x1D").unwrap(), "$1"), // italics 230 | (Regex::new(r"\x1F(.+)\x1F").unwrap(), "$1"), // underline 231 | // stripColors str.replace(/\x03\d{0,2}(,\d{0,2}|\x02\x02)?/g, '') 232 | (Regex::new(r"\x03\d{0,2}(,\d{0,2}|\x02\x02)?").unwrap(), ""), 233 | ]; 234 | } 235 | 236 | REPLACEMENTS.iter().fold(text.into(), |text, &(ref re, replacement)| { 237 | re.replace_all(&text, replacement) 238 | }) 239 | } 240 | 241 | impl<'a> slack::EventHandler for SlackHandler<'a> { 242 | fn on_event(&mut self, cli: &mut slack::RtmClient, event: Result, raw_json: &str) { 243 | match event { 244 | Ok(slack::Event::Message(ref message)) => match message { 245 | &slack::Message::Standard { channel: Some(ref channel), user: Some(ref user), text: Some(ref text), .. } if user == self.user_id => { 246 | lazy_static! { 247 | static ref PM_RE: Regex = Regex::new(r"^(\S+):\s+(.+)").unwrap(); 248 | } 249 | 250 | let text = parse_slack_text(&text, cli); 251 | 252 | if text.starts_with("%") { 253 | self.irc_tx.send(ToIrc::Raw(text[1..].into())).unwrap(); 254 | log_err(cli.send_message(channel, "_sent raw command_")); 255 | } else if channel == self.bot_channel { 256 | if let Some(captures) = PM_RE.captures(&text) { 257 | self.irc_tx.send(ToIrc::Message { to: captures.at(1).unwrap().into(), msg: captures.at(2).unwrap().into() }).unwrap(); 258 | } else { 259 | log_err(cli.send_message(channel, "_no message sent, are you missing a `user: ` prefix?_")); 260 | } 261 | } else { 262 | self.irc_tx.send(ToIrc::Message { to: format!("#{}", get_channel_with_id(cli, channel).expect("channel with id").name), msg: text.clone() }).unwrap(); 263 | } 264 | }, 265 | &slack::Message::MeMessage { ref channel, ref user, ref text, .. } if user == self.user_id => { 266 | let text = parse_slack_text(&text, cli); 267 | 268 | self.irc_tx.send(ToIrc::MeMessage { to: format!("#{}", get_channel_with_id(cli, channel).expect("channel with id").name), msg: text.clone() }).unwrap(); 269 | }, 270 | &slack::Message::ChannelTopic { ref user, ref topic, .. } if user == self.user_id => { 271 | if let Some(channel) = Json::from_str(raw_json).expect("ChannelTopic message json").find("channel").and_then(|j| j.as_string()) { 272 | self.irc_tx.send(ToIrc::Topic { chan: format!("#{}", get_channel_with_id(cli, channel).expect("channel with id").name), topic: topic.clone() }).unwrap(); 273 | } 274 | }, 275 | _ => { 276 | debug!("[SLACK] {:?}", event); 277 | }, 278 | }, 279 | Ok(slack::Event::PresenceChange { ref user, ref presence }) if user == self.user_id => { 280 | self.irc_tx.send(ToIrc::Away(presence != "active")).unwrap(); 281 | }, 282 | Ok(slack::Event::ChannelJoined { ref channel }) => { 283 | if cli.get_channels().into_iter().find(|c| c.id == channel.id).is_none() { 284 | // we haven't seen this channel before, so update channels 285 | cli.update_channels().expect("updating channels"); 286 | // also update the other `cli` instance 287 | self.slack_tx.send(ToSlack::UpdateChannels).unwrap(); 288 | } 289 | self.irc_tx.send(ToIrc::Join(format!("#{}", channel.name))).unwrap(); 290 | }, 291 | Ok(slack::Event::ChannelLeft { ref channel }) => { 292 | self.irc_tx.send(ToIrc::Part(format!("#{}", get_channel_with_id(cli, channel).expect("channel with id").name))).unwrap(); 293 | }, 294 | evt => { 295 | debug!("[SLACK] {:?}", evt); 296 | }, 297 | } 298 | } 299 | 300 | fn on_ping(&mut self, _cli: &mut slack::RtmClient) {} 301 | 302 | fn on_close(&mut self, _cli: &mut slack::RtmClient) { 303 | warn!("Disconnected from Slack"); 304 | } 305 | 306 | fn on_connect(&mut self, _cli: &mut slack::RtmClient) {} 307 | } 308 | 309 | fn post_message(cli: &slack::RtmClient, token: &str, to: &str, text: &str, username: Option<&str>) { 310 | let to: &str = if to.starts_with("#") { 311 | match cli.get_channel_id(&to[1..].to_lowercase()) { 312 | Some(id) => id, 313 | None => { 314 | error!("Failed to find channel '{}'", &to[1..]); 315 | return; 316 | }, 317 | } 318 | } else { 319 | to 320 | }; 321 | 322 | let client = hyper::Client::new(); 323 | let icon_url = username.map(|username| format!("http://api.adorable.io/avatars/48/{}.png", username)); 324 | let icon_url = icon_url.as_ref().map(|s| s.as_ref()); 325 | log_err(slack::api::chat::post_message( 326 | &client, 327 | token, 328 | to, 329 | text, 330 | username, 331 | Some(username.is_none()), // if no username, send `as_user` 332 | None, 333 | Some(false), 334 | None, 335 | None, 336 | None, 337 | icon_url, 338 | None, 339 | )); 340 | } 341 | 342 | fn main() { 343 | log::set_logger(|max_log_level| { 344 | max_log_level.set(LogLevelFilter::Info); 345 | Box::new(StdoutLogger) 346 | }).unwrap(); 347 | 348 | let c: Arc = { 349 | let mut s: String = String::new(); 350 | let file_path = env::args().skip(1).next().unwrap_or("config.toml".into()); 351 | let mut f = File::open(&file_path).expect(&format!("load config file {}", file_path)); 352 | f.read_to_string(&mut s).expect("reading from config file"); 353 | toml::decode_str::(&s).expect("parse config").into() 354 | }; 355 | 356 | let (slack_tx, slack_rx) = channel::(); 357 | let (irc_tx, irc_rx) = channel::(); 358 | let (irc_init_tx, irc_init_rx) = oneshot::channel::(); 359 | 360 | let slack_thread = { 361 | let c = c.clone(); 362 | let slack_tx = slack_tx.clone(); 363 | thread::Builder::new().name("slack".into()).spawn(move || { 364 | let mut cli = slack::RtmClient::new(&c.slack_token); 365 | cli.login().expect("logging into Slack"); 366 | 367 | // auto-join channels the bot has previously been invited to 368 | irc_init_tx.send(IrcInit { 369 | channels: cli.get_channels().into_iter() 370 | .filter(|c| c.is_member) 371 | .map(|c| format!("#{}", c.name)) 372 | .collect() 373 | }).unwrap(); 374 | 375 | let user_id = cli.get_user_id(&c.slack_user).expect("user id of Slack user").clone(); 376 | let bot_channel = cli.im_open(&user_id).expect("IM channel with Slack bot").channel.id; 377 | 378 | let recv_thread = { 379 | let c = c.clone(); 380 | let bot_channel = bot_channel.clone(); 381 | thread::Builder::new().name("slack_recv".into()).spawn(move || { 382 | let mut cli = slack::RtmClient::new(&c.slack_token); 383 | let mut handler = SlackHandler { 384 | irc_tx: &irc_tx, 385 | slack_tx: &slack_tx, 386 | user_id: &user_id, 387 | bot_channel: &bot_channel, 388 | }; 389 | 390 | while let Err(e) = cli.login_and_run(&mut handler) { 391 | slack_tx.send(ToSlack::Error(format!("{:?}", e))).unwrap(); 392 | thread::sleep(Duration::from_secs(1)); 393 | } 394 | }).unwrap() 395 | }; 396 | 397 | for msg in slack_rx { 398 | match msg { 399 | ToSlack::Message { to, from, msg } => { 400 | let to: &str = if to.starts_with("#") { 401 | match cli.get_channel_id(&to[1..].to_lowercase()) { 402 | Some(ref id) => id, 403 | None => return, 404 | } 405 | } else { 406 | &bot_channel 407 | }; 408 | 409 | post_message(&cli, &c.slack_token, to, &msg, Some(&from)); 410 | }, 411 | ToSlack::Topic { by, chan, topic } => { 412 | if let Some(by) = by { 413 | post_message(&cli, &c.slack_token, &chan, &format!("*{}* has changed the topic", by), None); 414 | } 415 | log_err(cli.set_topic(&chan.to_lowercase(), &topic.unwrap_or("".into()).chars().take(250).collect::())); 416 | }, 417 | ToSlack::Kick { by, chans, nicks, reason } => { 418 | let reason = &reason.unwrap_or("".into()); 419 | 420 | for chan in chans { 421 | for nick in &nicks { 422 | post_message(&cli, &c.slack_token, &chan, &format!("*{}* has kicked *{}* (_{}_)", by, nick, reason), None); 423 | } 424 | } 425 | }, 426 | ToSlack::Join { nick, chans } => { 427 | for chan in chans { 428 | post_message(&cli, &c.slack_token, &chan, &format!("*{}* has joined", nick), None); 429 | } 430 | }, 431 | ToSlack::Part { nick, chans, reason } => { 432 | let reason = &reason.unwrap_or("".into()); 433 | 434 | for chan in chans { 435 | post_message(&cli, &c.slack_token, &chan, &format!("*{}* has left (_{}_)", nick, reason), None); 436 | } 437 | }, 438 | ToSlack::Quit { nick, chans, reason } => { 439 | let reason = &reason.unwrap_or("".into()); 440 | 441 | for chan in chans { 442 | post_message(&cli, &c.slack_token, &chan, &format!("*{}* has quit (_{}_)", nick, reason), None); 443 | } 444 | }, 445 | ToSlack::Nick { old_nick, new_nick, chans } => { 446 | for chan in chans { 447 | post_message(&cli, &c.slack_token, &chan, &format!("*{}* is now known as *{}*", old_nick, new_nick), None); 448 | } 449 | }, 450 | ToSlack::Mode { by, name, modes, params } => { 451 | if name.starts_with("#") { 452 | post_message(&cli, &c.slack_token, &name, &format!("*{}* sets mode *{}* on _{}_", by, modes, params.unwrap_or(name.clone())), None); 453 | } 454 | }, 455 | ToSlack::Whois(nick, info) => { 456 | let info = match info { 457 | Whois::User { user, host, real_name } => { 458 | format!("(_{}@{}_): _{}_", user, host, real_name) 459 | }, 460 | Whois::Channels(chans) => { 461 | format!("is in channels {}", chans.join(", ")) 462 | }, 463 | Whois::Server { server, server_info } => { 464 | format!("_{}_ :_{}_", server, server_info) 465 | }, 466 | Whois::Away(reason) => { 467 | format!("is away (_{}_)", reason) 468 | }, 469 | Whois::Account { msg, account } => { 470 | format!("{} _{}_", msg, account) 471 | }, 472 | Whois::Idle { seconds, signon } => { 473 | format!("idle {}s, signon: {}", seconds, signon.to_local().rfc822()) 474 | }, 475 | Whois::Host(msg) | Whois::Modes(msg) | Whois::Registered(msg) | Whois::Secure(msg) | Whois::End(msg) => { 476 | msg 477 | }, 478 | }; 479 | 480 | post_message(&cli, &c.slack_token, &bot_channel, &format!("[*{}*] {}", nick, info), None); 481 | }, 482 | ToSlack::Error(msg) => { 483 | post_message(&cli, &c.slack_token, &bot_channel, &msg, None); 484 | }, 485 | ToSlack::UpdateChannels => { 486 | cli.update_channels().expect("updating channels"); 487 | }, 488 | } 489 | } 490 | 491 | recv_thread.join().expect("joining Slack recv thread"); 492 | }).unwrap() 493 | }; 494 | 495 | let irc_thread = { 496 | let c = c.clone(); 497 | thread::Builder::new().name("irc".into()).spawn(move || { 498 | let nick_to_chan = Arc::new(Mutex::new(MultiMap::::new())); 499 | 500 | let init = irc_init_rx.wait().expect("IRC initialization"); 501 | 502 | let config = irc::client::data::Config { 503 | nickname: Some(c.irc_nick.clone()), 504 | server: Some(c.irc_server.clone()), 505 | channels: Some(init.channels), 506 | port: c.irc_port, 507 | password: c.irc_password.clone(), 508 | use_ssl: c.irc_ssl.or(Some(true)), 509 | ..Default::default() 510 | }; 511 | 512 | let server = IrcServer::from_config(config).unwrap(); 513 | 514 | server.identify().expect("logging into IRC"); 515 | 516 | let server_thread = { 517 | let nick_to_chan = nick_to_chan.clone(); 518 | let server = server.clone(); 519 | thread::Builder::new().name("irc_server".into()).spawn(move || { 520 | for message in server.iter() { 521 | let message = message.expect("IRC message"); 522 | // extract the nick from the `nick!nick@hostname.com` 523 | let sender = message.prefix.and_then(|prefix| prefix.split("!").nth(0).map(Into::into)).unwrap_or("".into()); // only `None` for `Ping`, AFAICT 524 | match message.command { 525 | Command::ERROR(msg) => { 526 | slack_tx.send(ToSlack::Error(msg)).unwrap(); 527 | }, 528 | Command::PRIVMSG(to, text) | Command::NOTICE(to, text) => { 529 | lazy_static! { 530 | static ref ACTION_RE: Regex = Regex::new("^\x01ACTION (.+)\x01$").unwrap(); 531 | } 532 | 533 | let text = parse_irc_text(&text); 534 | 535 | if ACTION_RE.is_match(&text) { 536 | slack_tx.send(ToSlack::Message { 537 | to: to, 538 | from: sender, 539 | msg: format!("_{}_", ACTION_RE.captures(&text).unwrap().at(1).unwrap()) 540 | }).unwrap(); 541 | } else { 542 | slack_tx.send(ToSlack::Message { to: to, from: sender, msg: text }).unwrap(); 543 | } 544 | }, 545 | Command::TOPIC(channel, topic) => { 546 | // topic set by a user while we're in the channel 547 | slack_tx.send(ToSlack::Topic { by: Some(sender), chan: channel, topic: topic }).unwrap(); 548 | }, 549 | Command::KICK(channels, users, reason) => { 550 | let chans = channels.split(",").map(Into::into).collect::>(); 551 | let nicks = users.split(",").map(Into::into).collect(); 552 | 553 | { 554 | let mut nick_to_chan = nick_to_chan.lock().unwrap(); 555 | for nick in &nicks { 556 | if let Some(v) = nick_to_chan.get_vec_mut(nick) { 557 | v.retain(|c| !chans.contains(c)); 558 | } 559 | } 560 | } 561 | 562 | slack_tx.send(ToSlack::Kick { by: sender, chans: chans, nicks: nicks, reason: reason }).unwrap(); 563 | }, 564 | Command::JOIN(channels, _, _) => { 565 | let chans = channels.split(",").map(Into::into).collect::>(); 566 | 567 | { 568 | let mut nick_to_chan = nick_to_chan.lock().unwrap(); 569 | for chan in &chans { 570 | nick_to_chan.insert(sender.clone(), chan.clone()); 571 | } 572 | } 573 | 574 | slack_tx.send(ToSlack::Join { nick: sender, chans: chans }).unwrap(); 575 | }, 576 | Command::PART(channels, reason) => { 577 | let chans = channels.split(",").map(Into::into).collect::>(); 578 | 579 | if let Some(v) = nick_to_chan.lock().unwrap().get_vec_mut(&sender) { 580 | v.retain(|c| !chans.contains(c)); 581 | } 582 | 583 | slack_tx.send(ToSlack::Part { nick: sender, chans: chans, reason: reason }).unwrap(); 584 | }, 585 | Command::QUIT(reason) => { 586 | let mut nick_to_chan = nick_to_chan.lock().unwrap(); 587 | let chans = nick_to_chan.get_vec(&sender).map(|v| v.clone()).unwrap_or(vec![]); 588 | 589 | nick_to_chan.remove(&sender); 590 | 591 | slack_tx.send(ToSlack::Quit { nick: sender, chans: chans, reason: reason }).unwrap(); 592 | }, 593 | Command::NICK(nick) => { 594 | let mut nick_to_chan = nick_to_chan.lock().unwrap(); 595 | let chans = nick_to_chan.get_vec(&sender).map(|v| v.clone()).unwrap_or(vec![]); 596 | 597 | if let Some(mut v) = nick_to_chan.remove(&sender) { 598 | match nick_to_chan.entry(nick.clone()) { 599 | multimap::Entry::Occupied(mut e) => { 600 | warn!("'{}' changed nick to extant nick '{}'", sender, nick); 601 | let v_ = e.get_vec_mut(); 602 | v_.clear(); 603 | v_.append(&mut v); 604 | }, 605 | multimap::Entry::Vacant(e) => { 606 | e.insert_vec(v); 607 | }, 608 | } 609 | } 610 | 611 | slack_tx.send(ToSlack::Nick { old_nick: sender, new_nick: nick, chans: chans }).unwrap(); 612 | }, 613 | Command::MODE(name, modes, params) => { 614 | slack_tx.send(ToSlack::Mode { by: sender, name: name, modes: modes, params: params }).unwrap(); 615 | }, 616 | Command::Response(resp, args, suffix) => if resp.is_error() { 617 | // error responses 618 | slack_tx.send(ToSlack::Error(format!("{:?}: {} <{}>", resp, suffix.unwrap_or("".into()), args.join(" ")))).unwrap(); 619 | } else { 620 | match resp { 621 | Response::RPL_NOTOPIC | Response::RPL_TOPIC => { 622 | // response to topic request 623 | slack_tx.send(ToSlack::Topic { by: None, chan: args[1].clone(), topic: suffix }).unwrap(); 624 | }, 625 | Response::RPL_NAMREPLY => { 626 | let ref chan = args[2]; 627 | let mut nick_to_chan = nick_to_chan.lock().unwrap(); 628 | for nick in suffix.unwrap().trim().replace("@", "").replace("+", "").split(" ").map(Into::into) { 629 | if nick_to_chan.get_vec(&nick).map(|v| !v.contains(&chan)).unwrap_or(true) { 630 | nick_to_chan.insert(nick, chan.clone()); 631 | } 632 | } 633 | }, 634 | Response::RPL_WHOISUSER => { 635 | slack_tx.send(ToSlack::Whois(args[1].clone(), Whois::User { 636 | user: args[2].clone(), 637 | host: args[3].clone(), 638 | real_name: suffix.unwrap() 639 | })).unwrap(); 640 | }, 641 | Response::RPL_WHOISCHANNELS => { 642 | slack_tx.send(ToSlack::Whois(args[1].clone(), Whois::Channels( 643 | suffix.unwrap().trim().replace("@", "").replace("+", "").split(" ").map(Into::into).collect() 644 | ))).unwrap(); 645 | }, 646 | Response::RPL_WHOISSERVER => { 647 | slack_tx.send(ToSlack::Whois(args[1].clone(), Whois::Server { 648 | server: args[2].clone(), 649 | server_info: suffix.unwrap() 650 | })).unwrap(); 651 | }, 652 | Response::RPL_AWAY => { 653 | slack_tx.send(ToSlack::Whois(args[1].clone(), Whois::Away(suffix.unwrap()))).unwrap(); 654 | }, 655 | Response::RPL_WHOISIDLE => { 656 | slack_tx.send(ToSlack::Whois(args[1].clone(), Whois::Idle { 657 | seconds: args[2].parse().expect("seconds from idle time"), 658 | signon: time::strptime(&args[3], "%s").expect("time from signon time"), 659 | })).unwrap(); 660 | }, 661 | Response::RPL_ENDOFWHOIS => { 662 | slack_tx.send(ToSlack::Whois(args[1].clone(), Whois::End(suffix.unwrap()))).unwrap(); 663 | }, 664 | _ => { 665 | debug!("[IRC] {:?}", Command::Response(resp, args, suffix)); 666 | }, 667 | } 668 | }, 669 | Command::Raw(id, args, suffix) => match id.as_str() { 670 | "307" => { // RPL_WHOISREGNICK 671 | slack_tx.send(ToSlack::Whois(args[1].clone(), Whois::Registered(suffix.unwrap()))).unwrap(); 672 | }, 673 | "330" => { // RPL_WHOISACCOUNT 674 | slack_tx.send(ToSlack::Whois(args[1].clone(), Whois::Account { 675 | msg: suffix.unwrap(), 676 | account: args[2].clone() 677 | })).unwrap(); 678 | }, 679 | "378" => { // RPL_WHOISHOST 680 | slack_tx.send(ToSlack::Whois(args[1].clone(), Whois::Host(suffix.unwrap()))).unwrap(); 681 | }, 682 | "379" => { // RPL_WHOISMODES 683 | slack_tx.send(ToSlack::Whois(args[1].clone(), Whois::Modes(suffix.unwrap()))).unwrap(); 684 | }, 685 | "671" => { // RPL_WHOISSECURE 686 | slack_tx.send(ToSlack::Whois(args[1].clone(), Whois::Secure(suffix.unwrap()))).unwrap(); 687 | }, 688 | _ => { 689 | debug!("[IRC] {:?}", Command::Raw(id, args, suffix)); 690 | }, 691 | }, 692 | msg => { 693 | debug!("[IRC] {:?}", msg); 694 | }, 695 | } 696 | } 697 | }).unwrap() 698 | }; 699 | 700 | for msg in irc_rx { 701 | match msg { 702 | ToIrc::Message { to, msg } => { 703 | server.send_privmsg(&to, &msg).unwrap(); 704 | }, 705 | ToIrc::MeMessage { to, msg } => { 706 | server.send_privmsg(&to, &format!("\x01ACTION {}\x01", msg)).unwrap(); 707 | }, 708 | ToIrc::Raw(msg) => { 709 | server.send(Command::Raw(msg, vec![], None)).unwrap(); 710 | }, 711 | ToIrc::Away(is_away) => { 712 | server.send(Command::AWAY(Some(if is_away { " ".into() } else { "".into() }))).unwrap(); 713 | }, 714 | ToIrc::Join(chan) => { 715 | server.send_join(&chan).unwrap(); 716 | }, 717 | ToIrc::Part(chan) => { 718 | for (_, v) in nick_to_chan.lock().unwrap().iter_all_mut() { 719 | v.retain(|c| c != &chan); 720 | } 721 | 722 | server.send(Command::PART(chan, None)).unwrap(); 723 | }, 724 | ToIrc::Topic { chan, topic } => { 725 | server.send_topic(&chan, &topic).unwrap(); 726 | }, 727 | } 728 | } 729 | 730 | server_thread.join().unwrap(); 731 | }).unwrap() 732 | }; 733 | 734 | slack_thread.join().expect("join Slack thread"); 735 | irc_thread.join().expect("join IRC thread"); 736 | } 737 | --------------------------------------------------------------------------------