├── .gitignore ├── LICENSE ├── README.md ├── bot ├── .gitignore ├── lib │ ├── core_ext │ ├── framework │ ├── irc │ └── thread ├── res │ ├── brainfuck │ ├── coreutils │ ├── cpan_packages │ ├── crystal │ ├── distros │ ├── gem_names │ ├── git │ ├── glibc_functions │ ├── haskell │ ├── irssi │ ├── java_classes │ ├── javascript │ ├── perl │ ├── php │ ├── pokemon │ ├── ruby │ ├── unicode_codepoint_names │ ├── wordlist │ ├── wordlist_de │ └── wordlist_fr ├── settings.json.example └── src │ ├── main.cr │ ├── plugins │ ├── admin.cr │ ├── crystal_eval.cr │ ├── diaspora_version.cr │ ├── github_issues.cr │ ├── hangman.cr │ ├── hello_world.cr │ ├── key_value_store.cr │ ├── memo.cr │ ├── password.cr │ ├── programming_excuses.cr │ ├── russian_roulette.cr │ ├── what_the_commit.cr │ ├── wiki.cr │ └── wti_status.cr │ └── simple.cr ├── framework └── src │ └── framework │ ├── bot.cr │ ├── channel.cr │ ├── configuration.cr │ ├── event.cr │ ├── filter.cr │ ├── json_store.cr │ ├── limiter.cr │ ├── message.cr │ ├── plugin.cr │ ├── plugin_container.cr │ ├── timer.cr │ └── user.cr └── irc ├── spec ├── message_spec.cr └── modes_spec.cr └── src └── irc ├── channel.cr ├── connection.cr ├── mask.cr ├── membership.cr ├── message.cr ├── modes.cr ├── network.cr ├── user.cr ├── user_manager.cr └── workers.cr /.gitignore: -------------------------------------------------------------------------------- 1 | .crystal/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Jonne Haß 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CeBot 2 | 3 | CeBot is an IRC bot written in [Crystal](http://crystal-lang.org). 4 | 5 | Currently this repository contains not only the bot but also the underlying 6 | libraries and tools until they are mature enough to be extracted into their 7 | own projects. 8 | 9 | 10 | ## irc 11 | 12 | Base library that handles establishing and maintaining an connection to 13 | an IRC network, message de-/serialization and calling handlers for 14 | received messages. 15 | 16 | ### Dependencies 17 | 18 | * Crystal 19 | 20 | ## framework 21 | 22 | Abstraction layer to provide an easy to use API and commonly used tools 23 | for developing IRC bots. 24 | 25 | ### Dependencies 26 | 27 | * Crystal 28 | * irc 29 | 30 | ## bot 31 | 32 | The actual bot, consisting of plugins and a file to actually instantiate 33 | the bot. 34 | 35 | ### Dependencies 36 | 37 | * Crystal 38 | * framework -------------------------------------------------------------------------------- /bot/.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | settings.json 3 | -------------------------------------------------------------------------------- /bot/lib/core_ext: -------------------------------------------------------------------------------- 1 | ../../core_ext/src/core_ext -------------------------------------------------------------------------------- /bot/lib/framework: -------------------------------------------------------------------------------- 1 | ../../framework/src/framework -------------------------------------------------------------------------------- /bot/lib/irc: -------------------------------------------------------------------------------- 1 | ../../irc/src/irc -------------------------------------------------------------------------------- /bot/lib/thread: -------------------------------------------------------------------------------- 1 | ../../thread/src/thread -------------------------------------------------------------------------------- /bot/res/brainfuck: -------------------------------------------------------------------------------- 1 | << 2 | <<<< 3 | <<<<< 4 | <<<<<< 5 | <<<<<<<< 6 | <<<<<<<<<<< 7 | <<<<<<<<- 8 | <<<<<<<<[->>>>>>>>-<<<<<<<<<]>>[-<>>]< 9 | <<<<<<<- 10 | <<<<<<<[->>>>>>>>-<<<<<<<<<]>>[-<>>]<< 11 | <<<<<- 12 | <<<<[->>><<<<]; 13 | <<>>> 14 | <<- 15 | <<-- 16 | <<[-><<]<< 17 | <>> 18 | <>>>>> 19 | <- 20 | <[-><<<]>>>[-<<>>>]<<< 21 | <[->><<<<]>>>>[-<<<>>>>]<<< 22 | > 23 | >< 24 | >> 25 | >><<< 26 | >>> 27 | >>><<<< 28 | >>>> 29 | >>>>> 30 | >>>>><<<<< 31 | >>>>>> 32 | >>>>>><<<< 33 | >>>>>>><<<<< 34 | >>>>>>>> 35 | >>>>>>>>>> 36 | >>>>>>>>>>> 37 | >>>>>>>>>>>>> 38 | >>>>>>>[-<<<<<<>>>>>>>]<<<<<<< 39 | >>>>>>>[-<<<<<<>>>>>>>]<<<<<<<; 40 | >>>>>>[->-<] 41 | >>>>>>[-]<<<<<< 42 | >>>>>[-] 43 | >>>>>] 44 | >>>>[-<<< 45 | >>>>[-<<<< 46 | >>>>[-<<>>]>[->]<<<<< 47 | >>>>[-<>>>] 48 | >>>>[-<>>>]<<<<>>[-><<]<< 49 | >>>>[-<>>>]<<[-><<]<<_ 50 | >>>>[->>><<<<<]>>>[-<<>>>]<<<<<<< 51 | >>>>[->>>><<<<<]<<<< 52 | >>>>[->>>>>>>>>>>>><<<<<<<<<<<<<<] 53 | >>>>]<<<< 54 | >>>[-<< 55 | >>>[-<<< 56 | >>>[-<>>><<] 57 | >>>[-<->>]<[-<]<<__ 58 | >>>[->><<]>>[-<>>]<<<<< 59 | >>>[->>><<<<<]>>>>[-<<<>>>>]<<<<<<< 60 | >>>[->]>[-<>>]<<<< 61 | >>>]<<< 62 | >>--< 63 | >>[ 64 | >>[-<<[-]>>]<< 65 | >>[-<>>]< 66 | >>[-<>>]<; 67 | >>[-<]<< 68 | >>[-><<]<< 69 | >>[->>>><<<<<<]<< 70 | >>[->>>>><<<<<<<]<< 71 | >>[->>>>>>>>><<<<<<<<<<]<< 72 | >>[->>>>>>>>>>>><<<<<<<<<<<<<] 73 | >>[-[->>>><<<<<]>>[-]<<>[-<]<>>>>>]<< 74 | >>[-]<< 75 | >- 76 | >-[>[-]][ 78 | >[-< 79 | >[-<>>><<]<<<< 80 | >[-<-<<<<<->><<<------+>>>>>------+>]< 81 | >[-<]< 82 | >[-><<]< 83 | >[->>>><<<<<<]< 84 | >[->>>><<<<<] 85 | >[->>>><<<<<]< 86 | >[->>>>>>>>><<<<<<<<<<]< 87 | >[->>>>>>>-<<<<<<<<]>[->]< 88 | >[-[->>>><<<<<]>-[->>>><<<<<]<>>>>>]< 89 | >[-] 90 | >[-]>[-]>[-]>[-]> 91 | >]< 92 | __ 93 | ____ 94 | _____ 95 | _;; 96 | _;;; 97 | _;{;} 98 | - 99 | -<<<<<- 100 | ->[-<<<<>>>>>]<<<<<<- 101 | -- 102 | --- 103 | --->>>>>--- 104 | ----- 105 | ----->>>>>----- 106 | ------->>>>>------- 107 | ---------- 108 | -------------------------------- 109 | ----+<<<<<----+ 110 | --] 111 | -[ 112 | -[->>>><<<<<] 113 | -[->>>><<<<<]>>>>> 114 | ; 115 | ;; 116 | ;;;{;} 117 | ;{;}; 118 | ;{} 119 | : 120 | ::;: 121 | // 122 | . 123 | .---------- 124 | [ 125 | [>><<<<-]>>>>[<<<>>>>-]<<<< 126 | [>>>>>] 127 | [-<<<<>>>>>>]<<<<<[->>>><<<<<] 128 | [-<<<<>>>>>>]<<<<[->>><<<<]>>>>> 129 | [-<<<>>>>]; 130 | [-<<]>>[-<>>]<< 131 | [-><<] 132 | [->>>><<<<<<<->>>]<<[-><<]>>>> 133 | [->>>><<<<<<<->>>]<[-<]>> 134 | [->>>><<<<<<]>>>>>[-<<<<>>>>>]<<<<< 135 | [->>>><<<<<<]>>>>[-<<<>>>>] 136 | [->>>>>><<<<<<<] 137 | [->>>>>><<<<<<<]; 138 | [->>>>>>>><<<<<<<<<] 139 | [->>>>>>>><<<<<<<<<]>>>>>>>>> 140 | [->>>>>>>>><<<<<<<<<<] 141 | [->>>>>>>>><<<<<<<<<<]>>>>>>>>>> 142 | [->>>>>>>>>>>>><<<<<<<<<<<<<<<>>>>>[-<<<<>>>>>] 143 | [->>>>>>>>>>>>>><<<<<<<<<<<<<<<<>>>>>>[-<<<<<>>>>>>] 144 | [->>>>>>>-<<<<<<<<]>[->]>>>>>> 145 | [->>-<<]>> 146 | [->>->-<<<] 147 | [->]>[->]<<[-><<]> 148 | [-[->>>><<<<<]>>>>>]< 149 | [-] 150 | [-]<<<<<<<<< 151 | [-]>[-]>[-]>[-]>[-] 152 | [-]>[-]>[-]>[-]>[-]> 153 | ] 154 | ]< 155 | ]<< 156 | ]<<<<< 157 | + 158 | +> 159 | +>>> 160 | +>>>> 161 | +>>>>> 162 | +>>>+> 163 | -------------------------------------------------------------------------------- /bot/res/coreutils: -------------------------------------------------------------------------------- 1 | [ 2 | arch 3 | base64 4 | basename 5 | cat 6 | chcon 7 | chgrp 8 | chmod 9 | chown 10 | chroot 11 | cksum 12 | comm 13 | cp 14 | csplit 15 | cut 16 | date 17 | dd 18 | df 19 | dir 20 | dircolors 21 | dirname 22 | du 23 | echo 24 | env 25 | expand 26 | expr 27 | factor 28 | false 29 | fmt 30 | fold 31 | groups 32 | head 33 | hostid 34 | id 35 | install 36 | join 37 | link 38 | ln 39 | logname 40 | ls 41 | md5sum 42 | mkdir 43 | mkfifo 44 | mknod 45 | mktemp 46 | mv 47 | nice 48 | nl 49 | nohup 50 | nproc 51 | od 52 | paste 53 | pathchk 54 | pinky 55 | pr 56 | printenv 57 | printf 58 | ptx 59 | pwd 60 | readlink 61 | rm 62 | rmdir 63 | runcon 64 | runuser 65 | seq 66 | sha1sum 67 | sha224sum 68 | sha256sum 69 | sha384sum 70 | sha512sum 71 | shred 72 | shuf 73 | sleep 74 | sort 75 | split 76 | stat 77 | stdbuf 78 | stty 79 | su 80 | sum 81 | sync 82 | tac 83 | tail 84 | tee 85 | test 86 | timeout 87 | touch 88 | tr 89 | true 90 | truncate 91 | tsort 92 | tty 93 | uname 94 | unexpand 95 | uniq 96 | unlink 97 | users 98 | vdir 99 | wc 100 | who 101 | whoami 102 | yes 103 | addr2line 104 | ar 105 | as 106 | c++filt 107 | gprof 108 | ld 109 | nm 110 | objcopy 111 | objdump 112 | ranlib 113 | readelf 114 | size 115 | strings 116 | strip 117 | find 118 | oldfind 119 | xargs 120 | arping 121 | clockdiff 122 | ifenslave 123 | ping 124 | ping6 125 | rdisc 126 | tracepath 127 | tracepath6 128 | dig 129 | host 130 | nslookup 131 | nsupdate 132 | -------------------------------------------------------------------------------- /bot/res/distros: -------------------------------------------------------------------------------- 1 | 0Linux 2 | 2XOS 3 | 4MLinux 4 | Absolute 5 | Alpine 6 | ALT 7 | Android-x86 8 | Antergos 9 | antiX 10 | APODIO 11 | Arch 12 | ArchBang 13 | ArtistX 14 | AsteriskNOW 15 | AUSTRUMI 16 | AV Linux 17 | BackBox 18 | Baltix 19 | Bardinux 20 | Baruwa 21 | Bella 22 | Berry 23 | Bicom 24 | Bio-Linux 25 | BlackArch 26 | Black Lab 27 | blackPanther 28 | BLAG 29 | BlankOn 30 | Bodhi 31 | BOSS 32 | Bridge 33 | Burapha 34 | CAELinux 35 | CAINE 36 | Caixa Mágica 37 | Calculate 38 | Canaima 39 | Càtix 40 | CentOS 41 | Centrych 42 | Chakra 43 | ChaletOS 44 | Chitwanix 45 | Chromixium 46 | ClearOS 47 | Clonezilla 48 | Connochaet 49 | CoreOS 50 | CRUX 51 | Debian 52 | deepin 53 | DEFT 54 | Devil 55 | DoudouLinux 56 | DragonFly 57 | Edubuntu 58 | Elastix 59 | elementary 60 | Elive 61 | Emmabuntüs 62 | Endian 63 | Exe 64 | Exherbo 65 | ExTiX 66 | Fedora 67 | Fermi 68 | Finnix 69 | FreeBSD 70 | FreeNAS 71 | Frugalware 72 | FuguIta 73 | Funtoo 74 | GeeXboX 75 | Gentoo 76 | GhostBSD 77 | gNewSense 78 | GoboLinux 79 | GParted 80 | Greenie 81 | Grml 82 | Guadalinex 83 | Haiku 84 | HandyLinux 85 | Hanthana 86 | IPCop 87 | IPFire 88 | kademar 89 | Kali 90 | KANOTIX 91 | KaOS 92 | Karoshi 93 | KNOPPIX 94 | KolibriOS 95 | Korora 96 | Kubuntu 97 | Kwheezy 98 | Kwort 99 | KXStudio 100 | Leeenux 101 | Legacy 102 | LFS 103 | LinEx 104 | LinHES 105 | Linpus 106 | LinuxBBQ 107 | LinuxConsole 108 | Linuxfx 109 | Liquid Lemur 110 | Lite 111 | LliureX 112 | LPS 113 | Lubuntu 114 | Lunar 115 | LuninuX 116 | LXLE 117 | Macpup 118 | Madbox 119 | Mageia 120 | MakuluLinux 121 | Mangaka 122 | Manjaro 123 | Matriux 124 | MAX 125 | MEPIS 126 | MidnightBSD 127 | MiniNo 128 | MINIX 129 | Mint 130 | Miracle 131 | MirOS 132 | Momonga 133 | Musix 134 | Mythbuntu 135 | Nanolinux 136 | NAS4Free 137 | Neptune 138 | NetBSD 139 | NethServer 140 | Netrunner 141 | NetSecL 142 | NexentaStor 143 | NixOS 144 | Nova 145 | NST 146 | NuTyX 147 | Ojuba 148 | OLPC 149 | Omoikane 150 | OpenBSD 151 | OpenELEC 152 | OpenIndiana 153 | OpenLX 154 | openmamba 155 | OpenMandriva 156 | OpenMediaVault 157 | openSUSE 158 | Openwall 159 | Open Xange 160 | Ophcrack 161 | Oracle 162 | OSMC 163 | Overclockix 164 | Oz Unity 165 | paldo 166 | Parabola 167 | Pardus 168 | Parsix 169 | Parted Magic 170 | PC-BSD 171 | PCLinuxOS 172 | Peach OSI 173 | PelicanHPC 174 | Pentoo 175 | Peppermint 176 | pfSense 177 | Pidora 178 | Pinguy 179 | Pisi 180 | Plamo 181 | PLD 182 | Plop 183 | Point 184 | PoliArch 185 | Porteus 186 | Porteus Kiosk 187 | Proxmox 188 | Puppy 189 | Q4OS 190 | Qubes 191 | Quirky 192 | Raspbian 193 | ReactOS 194 | Rebellin 195 | Red Hat 196 | REMnux 197 | Rescatux 198 | RISC 199 | Robolinux 200 | Rocks Cluster 201 | ROSA 202 | Runtu 203 | Sabayon 204 | SalentOS 205 | Salix 206 | Scientific 207 | Securepoint 208 | SELKS 209 | Semplice 210 | siduction 211 | Simplicity 212 | Skolelinux 213 | Slackel 214 | Slackware 215 | SliTaz 216 | SmartOS 217 | SME Server 218 | Smoothwall 219 | SMS 220 | Solaris 221 | Solus 222 | SolydXK 223 | Sonar 224 | Sophos 225 | Sorcerer 226 | Source Mage 227 | SparkyLinux 228 | Springdale 229 | StartOS 230 | SteamOS 231 | Stella 232 | SuliX 233 | SuperX 234 | SUSE 235 | SymphonyOS 236 | SystemRescue 237 | T2 238 | Tails 239 | Tanglu 240 | Thinstation 241 | Tiny Core 242 | Toutou 243 | Trisquel 244 | Turbolinux 245 | TurnKey 246 | UberStudent 247 | Ubuntu 248 | Ubuntu DP 249 | Ubuntu GNOME 250 | Ubuntu Kylin 251 | Ubuntu MATE 252 | Ubuntu Studio 253 | UHU-Linux 254 | Ulteo 255 | Ultimate 256 | Univention 257 | Untangle 258 | UPR 259 | Vector 260 | Vine 261 | Vinux 262 | Void 263 | Volumio 264 | VortexBox 265 | Voyager 266 | VyOS 267 | wattOS 268 | Webconverger 269 | Wifislax 270 | WM Live 271 | XStreamOS 272 | Xubuntu 273 | Zentyal 274 | Zenwalk 275 | Zeroshell 276 | ZevenOS 277 | Zorin -------------------------------------------------------------------------------- /bot/res/git: -------------------------------------------------------------------------------- 1 | add 2 | add--interactive 3 | am 4 | annotate 5 | apply 6 | archive 7 | bisect 8 | bisect--helper 9 | blame 10 | branch 11 | bundle 12 | cat-file 13 | check-attr 14 | check-ignore 15 | check-mailmap 16 | checkout 17 | checkout-index 18 | check-ref-format 19 | cherry 20 | cherry-pick 21 | clean 22 | clone 23 | column 24 | commit 25 | commit-tree 26 | config 27 | count-objects 28 | credential 29 | credential-cache 30 | credential-cache--daemon 31 | credential-store 32 | daemon 33 | describe 34 | diff 35 | diff-files 36 | diff-index 37 | difftool 38 | difftool--helper 39 | diff-tree 40 | fast-export 41 | fast-import 42 | fetch 43 | fetch-pack 44 | filter-branch 45 | fmt-merge-msg 46 | for-each-ref 47 | format-patch 48 | fsck 49 | fsck-objects 50 | gc 51 | get-tar-commit-id 52 | grep 53 | hash-object 54 | help 55 | http-backend 56 | http-fetch 57 | http-push 58 | imap-send 59 | index-pack 60 | init 61 | init-db 62 | instaweb 63 | log 64 | ls-files 65 | ls-remote 66 | ls-tree 67 | mailinfo 68 | mailsplit 69 | merge 70 | merge-base 71 | merge-file 72 | merge-index 73 | merge-octopus 74 | merge-one-file 75 | merge-ours 76 | merge-recursive 77 | merge-resolve 78 | merge-subtree 79 | mergetool 80 | merge-tree 81 | mktag 82 | mktree 83 | mv 84 | name-rev 85 | notes 86 | p4 87 | pack-objects 88 | pack-redundant 89 | pack-refs 90 | patch-id 91 | prune 92 | prune-packed 93 | pull 94 | push 95 | quiltimport 96 | read-tree 97 | rebase 98 | receive-pack 99 | reflog 100 | relink 101 | remote 102 | remote-ext 103 | remote-fd 104 | remote-ftp 105 | remote-ftps 106 | remote-http 107 | remote-https 108 | remote-testsvn 109 | repack 110 | replace 111 | request-pull 112 | rerere 113 | reset 114 | revert 115 | rev-list 116 | rev-parse 117 | rm 118 | send-pack 119 | shell 120 | sh-i18n--envsubst 121 | shortlog 122 | show 123 | show-branch 124 | show-index 125 | show-ref 126 | stage 127 | stash 128 | status 129 | stripspace 130 | submodule 131 | subtree 132 | symbolic-ref 133 | tag 134 | unpack-file 135 | unpack-objects 136 | update-index 137 | update-ref 138 | update-server-info 139 | upload-archive 140 | upload-pack 141 | var 142 | verify-pack 143 | verify-tag 144 | web--browse 145 | whatchanged 146 | write-tree 147 | -------------------------------------------------------------------------------- /bot/res/glibc_functions: -------------------------------------------------------------------------------- 1 | strerror 2 | strerror_r 3 | perror 4 | error 5 | error_at_line 6 | warn 7 | vwarn 8 | warnx 9 | vwarnx 10 | err 11 | verr 12 | errx 13 | verrx 14 | malloc 15 | free 16 | cfree 17 | realloc 18 | calloc 19 | aligned_alloc 20 | memalign 21 | posix_memalign 22 | valloc 23 | mallopt 24 | mcheck 25 | mprobe 26 | mallinfo 27 | mtrace 28 | muntrace 29 | obstack_init 30 | obstack_alloc 31 | obstack_copy 32 | obstack_copy0 33 | obstack_free 34 | obstack_blank 35 | obstack_grow 36 | obstack_grow0 37 | obstack_1grow 38 | obstack_ptr_grow 39 | obstack_int_grow 40 | obstack_finish 41 | obstack_object_size 42 | obstack_room 43 | obstack_1grow_fast 44 | obstack_ptr_grow_fast 45 | obstack_int_grow_fast 46 | obstack_blank_fast 47 | obstack_base 48 | obstack_next_free 49 | obstack_object_size 50 | alloca 51 | brk 52 | mlock 53 | munlock 54 | mlockall 55 | munlockall 56 | islower 57 | isupper 58 | isalpha 59 | isdigit 60 | isalnum 61 | isxdigit 62 | ispunct 63 | isspace 64 | isblank 65 | isgraph 66 | isprint 67 | iscntrl 68 | isascii 69 | tolower 70 | toupper 71 | toascii 72 | _tolower 73 | _toupper 74 | wctype 75 | iswctype 76 | iswalnum 77 | iswalpha 78 | iswcntrl 79 | iswdigit 80 | iswgraph 81 | iswlower 82 | iswprint 83 | iswpunct 84 | iswspace 85 | iswupper 86 | iswxdigit 87 | iswblank 88 | wctrans 89 | towctrans 90 | towlower 91 | towupper 92 | strlen 93 | wcslen 94 | strnlen 95 | wcsnlen 96 | memcpy 97 | wmemcpy 98 | mempcpy 99 | wmempcpy 100 | memmove 101 | wmemmove 102 | memccpy 103 | memset 104 | wmemset 105 | strcpy 106 | wcscpy 107 | strncpy 108 | wcsncpy 109 | strdup 110 | wcsdup 111 | strndup 112 | stpcpy 113 | wcpcpy 114 | stpncpy 115 | wcpncpy 116 | strcat 117 | wcscat 118 | strncat 119 | wcsncat 120 | bcopy 121 | bzero 122 | memcmp 123 | wmemcmp 124 | strcmp 125 | wcscmp 126 | strcasecmp 127 | wcscasecmp 128 | strncmp 129 | wcsncmp 130 | strncasecmp 131 | wcsncasecmp 132 | strverscmp 133 | bcmp 134 | strcoll 135 | wcscoll 136 | strxfrm 137 | wcsxfrm 138 | memchr 139 | wmemchr 140 | rawmemchr 141 | memrchr 142 | strchr 143 | wcschr 144 | strchrnul 145 | wcschrnul 146 | strrchr 147 | wcsrchr 148 | strstr 149 | wcsstr 150 | wcswcs 151 | strcasestr 152 | memmem 153 | strspn 154 | wcsspn 155 | strcspn 156 | wcscspn 157 | strpbrk 158 | wcspbrk 159 | index 160 | rindex 161 | strtok 162 | wcstok 163 | strtok_r 164 | strsep 165 | basename 166 | basename 167 | dirname 168 | strfry 169 | memfrob 170 | l64a 171 | a64l 172 | argz_create 173 | argz_create_sep 174 | argz_count 175 | argz_extract 176 | argz_stringify 177 | argz_add 178 | argz_add_sep 179 | argz_append 180 | argz_delete 181 | argz_insert 182 | argz_next 183 | argz_replace 184 | envz_entry 185 | envz_get 186 | envz_add 187 | envz_merge 188 | envz_strip 189 | mbsinit 190 | btowc 191 | wctob 192 | mbrtowc 193 | mbrlen 194 | wcrtomb 195 | mbsrtowcs 196 | wcsrtombs 197 | mbsnrtowcs 198 | wcsnrtombs 199 | mbtowc 200 | wctomb 201 | mblen 202 | mbstowcs 203 | wcstombs 204 | iconv_open 205 | iconv_close 206 | iconv 207 | setlocale 208 | localeconv 209 | nl_langinfo 210 | strfmon 211 | rpmatch 212 | catopen 213 | catgets 214 | catclose 215 | gettext 216 | dgettext 217 | dcgettext 218 | textdomain 219 | bindtextdomain 220 | ngettext 221 | dngettext 222 | dcngettext 223 | bind_textdomain_codeset 224 | lfind 225 | lsearch 226 | bsearch 227 | qsort 228 | hcreate 229 | hdestroy 230 | hsearch 231 | hcreate_r 232 | hdestroy_r 233 | hsearch_r 234 | tsearch 235 | tfind 236 | tdelete 237 | tdestroy 238 | twalk 239 | fnmatch 240 | glob 241 | glob64 242 | globfree 243 | globfree64 244 | regcomp 245 | regexec 246 | regfree 247 | regerror 248 | wordexp 249 | wordfree 250 | fopen 251 | fopen64 252 | freopen 253 | freopen64 254 | __freadable 255 | __fwritable 256 | __freading 257 | __fwriting 258 | fclose 259 | fcloseall 260 | flockfile 261 | ftrylockfile 262 | funlockfile 263 | __fsetlocking 264 | fwide 265 | fputc 266 | fputwc 267 | fputc_unlocked 268 | fputwc_unlocked 269 | putc 270 | putwc 271 | putc_unlocked 272 | putwc_unlocked 273 | putchar 274 | putwchar 275 | putchar_unlocked 276 | putwchar_unlocked 277 | fputs 278 | fputws 279 | fputs_unlocked 280 | fputws_unlocked 281 | puts 282 | putw 283 | fgetc 284 | fgetwc 285 | fgetc_unlocked 286 | fgetwc_unlocked 287 | getc 288 | getwc 289 | getc_unlocked 290 | getwc_unlocked 291 | getchar 292 | getwchar 293 | getchar_unlocked 294 | getwchar_unlocked 295 | getw 296 | getline 297 | getdelim 298 | fgets 299 | fgetws 300 | fgets_unlocked 301 | fgetws_unlocked 302 | ungetc 303 | ungetwc 304 | fread 305 | fread_unlocked 306 | fwrite 307 | fwrite_unlocked 308 | printf 309 | wprintf 310 | fprintf 311 | fwprintf 312 | sprintf 313 | swprintf 314 | snprintf 315 | asprintf 316 | obstack_printf 317 | vprintf 318 | vwprintf 319 | vfprintf 320 | vfwprintf 321 | vsprintf 322 | vswprintf 323 | vsnprintf 324 | vasprintf 325 | obstack_vprintf 326 | parse_printf_format 327 | register_printf_function 328 | printf_size 329 | printf_size_info 330 | scanf 331 | wscanf 332 | fscanf 333 | fwscanf 334 | sscanf 335 | swscanf 336 | vscanf 337 | vwscanf 338 | vfscanf 339 | vfwscanf 340 | vsscanf 341 | vswscanf 342 | feof 343 | feof_unlocked 344 | ferror 345 | ferror_unlocked 346 | clearerr 347 | clearerr_unlocked 348 | ftell 349 | ftello 350 | ftello64 351 | fseek 352 | fseeko 353 | fseeko64 354 | rewind 355 | fgetpos 356 | fgetpos64 357 | fsetpos 358 | fsetpos64 359 | fflush 360 | fflush_unlocked 361 | _flushlbf 362 | __fpurge 363 | setvbuf 364 | setbuf 365 | setbuffer 366 | setlinebuf 367 | __flbf 368 | __fbufsize 369 | __fpending 370 | fmemopen 371 | open_memstream 372 | fopencookie 373 | fmtmsg 374 | addseverity 375 | open 376 | open64 377 | close 378 | read 379 | pread 380 | pread64 381 | write 382 | pwrite 383 | pwrite64 384 | lseek 385 | lseek64 386 | fdopen 387 | fileno 388 | fileno_unlocked 389 | readv 390 | writev 391 | mmap 392 | mmap64 393 | munmap 394 | msync 395 | mremap 396 | madvise 397 | shm_open 398 | shm_unlink 399 | select 400 | sync 401 | fsync 402 | fdatasync 403 | aio_read 404 | aio_read64 405 | aio_write 406 | aio_write64 407 | lio_listio 408 | lio_listio64 409 | aio_error 410 | aio_error64 411 | aio_return 412 | aio_return64 413 | aio_fsync 414 | aio_fsync64 415 | aio_suspend 416 | aio_suspend64 417 | aio_cancel 418 | aio_cancel64 419 | aio_init 420 | fcntl 421 | dup 422 | dup2 423 | ioctl 424 | getcwd 425 | getwd 426 | get_current_dir_name 427 | chdir 428 | fchdir 429 | opendir 430 | fdopendir 431 | dirfd 432 | readdir 433 | readdir_r 434 | readdir64 435 | readdir64_r 436 | closedir 437 | rewinddir 438 | telldir 439 | seekdir 440 | scandir 441 | alphasort 442 | versionsort 443 | scandir64 444 | alphasort64 445 | versionsort64 446 | ftw 447 | ftw64 448 | nftw 449 | nftw64 450 | link 451 | symlink 452 | readlink 453 | canonicalize_file_name 454 | realpath 455 | unlink 456 | rmdir 457 | remove 458 | rename 459 | mkdir 460 | stat 461 | stat64 462 | fstat 463 | fstat64 464 | lstat 465 | lstat64 466 | chown 467 | fchown 468 | umask 469 | getumask 470 | chmod 471 | fchmod 472 | access 473 | utime 474 | utimes 475 | lutimes 476 | futimes 477 | truncate 478 | truncate64 479 | ftruncate 480 | ftruncate64 481 | mknod 482 | tmpfile 483 | tmpfile64 484 | tmpnam 485 | tmpnam_r 486 | tempnam 487 | mktemp 488 | mkstemp 489 | mkdtemp 490 | pipe 491 | popen 492 | pclose 493 | mkfifo 494 | bind 495 | getsockname 496 | if_nametoindex 497 | if_indextoname 498 | if_nameindex 499 | if_freenameindex 500 | inet_aton 501 | inet_addr 502 | inet_network 503 | inet_ntoa 504 | inet_makeaddr 505 | inet_lnaof 506 | inet_netof 507 | inet_pton 508 | inet_ntop 509 | gethostbyname 510 | gethostbyname2 511 | gethostbyaddr 512 | gethostbyname_r 513 | gethostbyname2_r 514 | gethostbyaddr_r 515 | sethostent 516 | gethostent 517 | endhostent 518 | getservbyname 519 | getservbyport 520 | setservent 521 | getservent 522 | endservent 523 | htons 524 | ntohs 525 | htonl 526 | ntohl 527 | getprotobyname 528 | getprotobynumber 529 | setprotoent 530 | getprotoent 531 | endprotoent 532 | socket 533 | shutdown 534 | socketpair 535 | connect 536 | listen 537 | accept 538 | getpeername 539 | send 540 | recv 541 | sendto 542 | recvfrom 543 | getsockopt 544 | setsockopt 545 | getnetbyname 546 | getnetbyaddr 547 | setnetent 548 | getnetent 549 | endnetent 550 | isatty 551 | ttyname 552 | ttyname_r 553 | tcgetattr 554 | tcsetattr 555 | cfgetospeed 556 | cfgetispeed 557 | cfsetospeed 558 | cfsetispeed 559 | cfsetspeed 560 | cfmakeraw 561 | gtty 562 | stty 563 | tcsendbreak 564 | tcdrain 565 | tcflush 566 | tcflow 567 | getpt 568 | grantpt 569 | unlockpt 570 | ptsname 571 | ptsname_r 572 | openpty 573 | forkpty 574 | openlog 575 | syslog 576 | vsyslog 577 | closelog 578 | setlogmask 579 | sin 580 | sinf 581 | sinl 582 | cos 583 | cosf 584 | cosl 585 | tan 586 | tanf 587 | tanl 588 | sincos 589 | sincosf 590 | sincosl 591 | csin 592 | csinf 593 | csinl 594 | ccos 595 | ccosf 596 | ccosl 597 | ctan 598 | ctanf 599 | ctanl 600 | asin 601 | asinf 602 | asinl 603 | acos 604 | acosf 605 | acosl 606 | atan 607 | atanf 608 | atanl 609 | atan2 610 | atan2f 611 | atan2l 612 | casin 613 | casinf 614 | casinl 615 | cacos 616 | cacosf 617 | cacosl 618 | catan 619 | catanf 620 | catanl 621 | exp 622 | expf 623 | expl 624 | exp2 625 | exp2f 626 | exp2l 627 | exp10 628 | exp10f 629 | exp10l 630 | pow10 631 | pow10f 632 | pow10l 633 | log 634 | logf 635 | logl 636 | log10 637 | log10f 638 | log10l 639 | log2 640 | log2f 641 | log2l 642 | logb 643 | logbf 644 | logbl 645 | ilogb 646 | ilogbf 647 | ilogbl 648 | pow 649 | powf 650 | powl 651 | sqrt 652 | sqrtf 653 | sqrtl 654 | cbrt 655 | cbrtf 656 | cbrtl 657 | hypot 658 | hypotf 659 | hypotl 660 | expm1 661 | expm1f 662 | expm1l 663 | log1p 664 | log1pf 665 | log1pl 666 | cexp 667 | cexpf 668 | cexpl 669 | clog 670 | clogf 671 | clogl 672 | clog10 673 | clog10f 674 | clog10l 675 | csqrt 676 | csqrtf 677 | csqrtl 678 | cpow 679 | cpowf 680 | cpowl 681 | sinh 682 | sinhf 683 | sinhl 684 | cosh 685 | coshf 686 | coshl 687 | tanh 688 | tanhf 689 | tanhl 690 | csinh 691 | csinhf 692 | csinhl 693 | ccosh 694 | ccoshf 695 | ccoshl 696 | ctanh 697 | ctanhf 698 | ctanhl 699 | asinh 700 | asinhf 701 | asinhl 702 | acosh 703 | acoshf 704 | acoshl 705 | atanh 706 | atanhf 707 | atanhl 708 | casinh 709 | casinhf 710 | casinhl 711 | cacosh 712 | cacoshf 713 | cacoshl 714 | catanh 715 | catanhf 716 | catanhl 717 | erf 718 | erff 719 | erfl 720 | erfc 721 | erfcf 722 | erfcl 723 | lgamma 724 | lgammaf 725 | lgammal 726 | lgamma_r 727 | lgammaf_r 728 | lgammal_r 729 | gamma 730 | gammaf 731 | gammal 732 | tgamma 733 | tgammaf 734 | tgammal 735 | j0 736 | j0f 737 | j0l 738 | j1 739 | j1f 740 | j1l 741 | jn 742 | jnf 743 | jnl 744 | y0 745 | y0f 746 | y0l 747 | y1 748 | y1f 749 | y1l 750 | yn 751 | ynf 752 | ynl 753 | rand 754 | srand 755 | rand_r 756 | random 757 | srandom 758 | initstate 759 | setstate 760 | random_r 761 | srandom_r 762 | initstate_r 763 | setstate_r 764 | drand48 765 | erand48 766 | lrand48 767 | nrand48 768 | mrand48 769 | jrand48 770 | srand48 771 | seed48 772 | lcong48 773 | drand48_r 774 | erand48_r 775 | lrand48_r 776 | nrand48_r 777 | mrand48_r 778 | jrand48_r 779 | srand48_r 780 | seed48_r 781 | lcong48_r 782 | div 783 | ldiv 784 | lldiv 785 | imaxdiv 786 | isinf 787 | isinff 788 | isinfl 789 | isnan 790 | isnanf 791 | isnanl 792 | finite 793 | finitef 794 | finitel 795 | feclearexcept 796 | feraiseexcept 797 | fetestexcept 798 | fegetexceptflag 799 | fesetexceptflag 800 | fegetround 801 | fesetround 802 | fegetenv 803 | feholdexcept 804 | fesetenv 805 | feupdateenv 806 | feenableexcept 807 | fedisableexcept 808 | fegetexcept 809 | abs 810 | labs 811 | llabs 812 | imaxabs 813 | fabs 814 | fabsf 815 | fabsl 816 | cabs 817 | cabsf 818 | cabsl 819 | frexp 820 | frexpf 821 | frexpl 822 | ldexp 823 | ldexpf 824 | ldexpl 825 | scalb 826 | scalbf 827 | scalbl 828 | scalbn 829 | scalbnf 830 | scalbnl 831 | scalbln 832 | scalblnf 833 | scalblnl 834 | significand 835 | significandf 836 | significandl 837 | ceil 838 | ceilf 839 | ceill 840 | floor 841 | floorf 842 | floorl 843 | trunc 844 | truncf 845 | truncl 846 | rint 847 | rintf 848 | rintl 849 | nearbyint 850 | nearbyintf 851 | nearbyintl 852 | round 853 | roundf 854 | roundl 855 | lrint 856 | lrintf 857 | lrintl 858 | llrint 859 | llrintf 860 | llrintl 861 | lround 862 | lroundf 863 | lroundl 864 | llround 865 | llroundf 866 | llroundl 867 | modf 868 | modff 869 | modfl 870 | fmod 871 | fmodf 872 | fmodl 873 | drem 874 | dremf 875 | dreml 876 | remainder 877 | remainderf 878 | remainderl 879 | copysign 880 | copysignf 881 | copysignl 882 | signbit 883 | nextafter 884 | nextafterf 885 | nextafterl 886 | nexttoward 887 | nexttowardf 888 | nexttowardl 889 | nan 890 | nanf 891 | nanl 892 | fmin 893 | fminf 894 | fminl 895 | fmax 896 | fmaxf 897 | fmaxl 898 | fdim 899 | fdimf 900 | fdiml 901 | fma 902 | fmaf 903 | fmal 904 | creal 905 | crealf 906 | creall 907 | cimag 908 | cimagf 909 | cimagl 910 | conj 911 | conjf 912 | conjl 913 | carg 914 | cargf 915 | cargl 916 | cproj 917 | cprojf 918 | cprojl 919 | strtol 920 | wcstol 921 | strtoul 922 | wcstoul 923 | strtoll 924 | wcstoll 925 | strtoq 926 | wcstoq 927 | strtoull 928 | wcstoull 929 | strtouq 930 | wcstouq 931 | strtoimax 932 | wcstoimax 933 | strtoumax 934 | wcstoumax 935 | atol 936 | atoi 937 | atoll 938 | strtod 939 | strtof 940 | strtold 941 | wcstod 942 | wcstof 943 | wcstold 944 | atof 945 | ecvt 946 | fcvt 947 | gcvt 948 | qecvt 949 | qfcvt 950 | qgcvt 951 | ecvt_r 952 | fcvt_r 953 | qecvt_r 954 | qfcvt_r 955 | difftime 956 | clock 957 | times 958 | time 959 | stime 960 | gettimeofday 961 | settimeofday 962 | adjtime 963 | adjtimex 964 | localtime 965 | localtime_r 966 | gmtime 967 | gmtime_r 968 | mktime 969 | timelocal 970 | timegm 971 | ntp_gettime 972 | ntp_adjtime 973 | asctime 974 | asctime_r 975 | ctime 976 | ctime_r 977 | strftime 978 | wcsftime 979 | strptime 980 | getdate 981 | getdate_r 982 | tzset 983 | setitimer 984 | getitimer 985 | alarm 986 | sleep 987 | nanosleep 988 | getrusage 989 | vtimes 990 | getrlimit 991 | getrlimit64 992 | setrlimit 993 | setrlimit64 994 | ulimit 995 | vlimit 996 | sched_setscheduler 997 | sched_getscheduler 998 | sched_setparam 999 | sched_getparam 1000 | sched_get_priority_min 1001 | sched_get_priority_max 1002 | sched_rr_get_interval 1003 | sched_yield 1004 | getpriority 1005 | setpriority 1006 | nice 1007 | sched_getaffinity 1008 | sched_setaffinity 1009 | getpagesize 1010 | get_phys_pages 1011 | get_avphys_pages 1012 | get_nprocs_conf 1013 | get_nprocs 1014 | getloadavg 1015 | longjmp 1016 | sigsetjmp 1017 | siglongjmp 1018 | getcontext 1019 | makecontext 1020 | setcontext 1021 | swapcontext 1022 | strsignal 1023 | psignal 1024 | signal 1025 | sysv_signal 1026 | ssignal 1027 | sigaction 1028 | raise 1029 | gsignal 1030 | kill 1031 | killpg 1032 | sigemptyset 1033 | sigfillset 1034 | sigaddset 1035 | sigdelset 1036 | sigismember 1037 | sigprocmask 1038 | sigpending 1039 | pause 1040 | sigsuspend 1041 | sigaltstack 1042 | sigstack 1043 | siginterrupt 1044 | sigblock 1045 | sigsetmask 1046 | sigpause 1047 | getopt 1048 | getopt_long 1049 | getopt_long_only 1050 | argp_parse 1051 | argp_usage 1052 | argp_error 1053 | argp_failure 1054 | argp_state_help 1055 | argp_help 1056 | getsubopt 1057 | getenv 1058 | secure_getenv 1059 | putenv 1060 | setenv 1061 | unsetenv 1062 | clearenv 1063 | getauxval 1064 | syscall 1065 | exit 1066 | atexit 1067 | on_exit 1068 | abort 1069 | _exit 1070 | system 1071 | getpid 1072 | getppid 1073 | fork 1074 | vfork 1075 | execv 1076 | execl 1077 | execve 1078 | execle 1079 | execvp 1080 | execlp 1081 | waitpid 1082 | wait 1083 | wait4 1084 | wait3 1085 | semctl 1086 | semget 1087 | semop 1088 | semtimedop 1089 | sem_init 1090 | sem_destroy 1091 | sem_close 1092 | sem_unlink 1093 | sem_wait 1094 | sem_timedwait 1095 | sem_trywait 1096 | sem_post 1097 | sem_getvalue 1098 | ctermid 1099 | setsid 1100 | getsid 1101 | getpgrp 1102 | getpgid 1103 | setpgid 1104 | setpgrp 1105 | tcgetpgrp 1106 | tcsetpgrp 1107 | tcgetsid 1108 | getuid 1109 | getgid 1110 | geteuid 1111 | getegid 1112 | getgroups 1113 | seteuid 1114 | setuid 1115 | setreuid 1116 | setegid 1117 | setgid 1118 | setregid 1119 | setgroups 1120 | initgroups 1121 | getgrouplist 1122 | getlogin 1123 | cuserid 1124 | setutent 1125 | getutent 1126 | endutent 1127 | getutid 1128 | getutline 1129 | pututline 1130 | getutent_r 1131 | getutid_r 1132 | getutline_r 1133 | utmpname 1134 | updwtmp 1135 | setutxent 1136 | getutxent 1137 | endutxent 1138 | getutxid 1139 | getutxline 1140 | pututxline 1141 | utmpxname 1142 | getutmp 1143 | getutmpx 1144 | login_tty 1145 | login 1146 | logout 1147 | logwtmp 1148 | getpwuid 1149 | getpwuid_r 1150 | getpwnam 1151 | getpwnam_r 1152 | fgetpwent 1153 | fgetpwent_r 1154 | setpwent 1155 | getpwent 1156 | getpwent_r 1157 | endpwent 1158 | putpwent 1159 | getgrgid 1160 | getgrgid_r 1161 | getgrnam 1162 | getgrnam_r 1163 | fgetgrent 1164 | fgetgrent_r 1165 | setgrent 1166 | getgrent 1167 | getgrent_r 1168 | endgrent 1169 | setnetgrent 1170 | getnetgrent 1171 | getnetgrent_r 1172 | endnetgrent 1173 | innetgr 1174 | gethostname 1175 | sethostname 1176 | getdomainnname 1177 | setdomainname 1178 | gethostid 1179 | sethostid 1180 | uname 1181 | setfsent 1182 | endfsent 1183 | getfsent 1184 | getfsspec 1185 | getfsfile 1186 | setmntent 1187 | endmntent 1188 | getmntent 1189 | getmntent_r 1190 | addmntent 1191 | hasmntopt 1192 | mount 1193 | umount2 1194 | umount 1195 | sysctl 1196 | sysconf 1197 | pathconf 1198 | fpathconf 1199 | confstr 1200 | getpass 1201 | crypt 1202 | crypt_r 1203 | setkey 1204 | encrypt 1205 | setkey_r 1206 | encrypt_r 1207 | ecb_crypt 1208 | cbc_crypt 1209 | des_setparity 1210 | backtrace 1211 | backtrace_symbols 1212 | backtrace_symbols_fd 1213 | pthread_key_create 1214 | pthread_key_delete 1215 | pthread_setspecific 1216 | pthread_getattr_default_np 1217 | pthread_setattr_default_np 1218 | __ppc_get_timebase 1219 | __ppc_get_timebase_freq 1220 | __ppc_yield 1221 | __ppc_mdoio 1222 | __ppc_mdoom 1223 | __ppc_set_ppr_med 1224 | __ppc_set_ppr_low 1225 | __ppc_set_ppr_med_low 1226 | -------------------------------------------------------------------------------- /bot/res/haskell: -------------------------------------------------------------------------------- 1 | AbsoluteSeek 2 | AllocationLimitExceeded 3 | Alternative 4 | AppendMode 5 | Applicative 6 | ArithException 7 | ArrayException 8 | AssertionFailed 9 | AsyncException 10 | BlockBuffering 11 | BlockedIndefinitelyOnMVar 12 | BlockedIndefinitelyOnSTM 13 | Bool 14 | Bounded 15 | BufferMode 16 | CRLF 17 | Char 18 | Const 19 | Deadlock 20 | Denormal 21 | DivideByZero 22 | Double 23 | EQ 24 | Either 25 | Enum 26 | Eq 27 | ErrorCall 28 | Exception 29 | False 30 | FilePath 31 | Float 32 | Floating 33 | Foldable 34 | Fractional 35 | Functor 36 | GT 37 | Handle 38 | HandlePosn 39 | Handler 40 | HeapOverflow 41 | IO 42 | IOError 43 | IOException 44 | IOMode 45 | IndexOutOfBounds 46 | Int 47 | Integer 48 | Integral 49 | Just 50 | LF 51 | LT 52 | Left 53 | LineBuffering 54 | LossOfPrecision 55 | MaskedInterruptible 56 | MaskedUninterruptible 57 | MaskingState 58 | Maybe 59 | Monad 60 | MonadPlus 61 | Monoid 62 | NestedAtomically 63 | Newline 64 | NewlineMode 65 | NoBuffering 66 | NoMethodError 67 | NonTermination 68 | Nothing 69 | Num 70 | Ord 71 | Ordering 72 | Overflow 73 | PatternMatchFail 74 | RatioZeroDenominator 75 | Rational 76 | Read 77 | ReadMode 78 | ReadS 79 | ReadWriteMode 80 | Real 81 | RealFloat 82 | RealFrac 83 | RecConError 84 | RecSelError 85 | RecUpdError 86 | RelativeSeek 87 | Right 88 | SeekFromEnd 89 | SeekMode 90 | Show 91 | ShowS 92 | SomeAsyncException 93 | SomeException 94 | StackOverflow 95 | String 96 | TextEncoding 97 | ThreadKilled 98 | Traversable 99 | True 100 | UndefinedElement 101 | Underflow 102 | Unmasked 103 | UserInterrupt 104 | Word 105 | WrappedArrow 106 | WrappedMonad 107 | WriteMode 108 | ZipList 109 | abs 110 | all 111 | allowInterrupt 112 | and 113 | any 114 | ap 115 | appendFile 116 | asTypeOf 117 | asin 118 | asinh 119 | assert 120 | asyncExceptionFromException 121 | asyncExceptionToException 122 | atan2 123 | bracket 124 | bracketOnError 125 | bracket_ 126 | break 127 | catMaybes 128 | catch 129 | catchJust 130 | catches 131 | ceiling 132 | char8 133 | compare 134 | concat 135 | concatMap 136 | const 137 | curry 138 | cycle 139 | decodeFloat 140 | delete 141 | deleteBy 142 | deleteFirstsBy 143 | displayException 144 | div 145 | divMod 146 | drop 147 | dropWhile 148 | dropWhileEnd 149 | either 150 | elem 151 | elemIndex 152 | elemIndices 153 | empty 154 | encodeFloat 155 | enumFrom 156 | enumFromThen 157 | enumFromThenTo 158 | enumFromTo 159 | error 160 | evaluate 161 | even 162 | exp 163 | exponent 164 | fail 165 | filter 166 | filterM 167 | finally 168 | find 169 | findIndex 170 | findIndices 171 | fixIO 172 | flip 173 | floatDigits 174 | floatRadix 175 | floatRange 176 | floor 177 | fmap 178 | foldM 179 | foldM_ 180 | foldMap 181 | foldl 182 | foldl' 183 | foldl1 184 | foldl1' 185 | foldr 186 | foldr1 187 | forM 188 | forM_ 189 | forever 190 | fromEnum 191 | fromException 192 | fromInteger 193 | fromIntegral 194 | fromJust 195 | fromMaybe 196 | fromRational 197 | fst 198 | gcd 199 | genericDrop 200 | genericIndex 201 | genericLength 202 | genericReplicate 203 | genericSplitAt 204 | genericTake 205 | getChar 206 | getConst 207 | getContents 208 | getLine 209 | getMaskingState 210 | getZipList 211 | group 212 | groupBy 213 | guard 214 | hClose 215 | hFileSize 216 | hFlush 217 | hGetBuf 218 | hGetBufNonBlocking 219 | hGetBufSome 220 | hGetBuffering 221 | hGetChar 222 | hGetContents 223 | hGetEcho 224 | hGetEncoding 225 | hGetLine 226 | hGetPosn 227 | hIsClosed 228 | hIsEOF 229 | hIsOpen 230 | hIsReadable 231 | hIsSeekable 232 | hIsTerminalDevice 233 | hIsWritable 234 | hLookAhead 235 | hPrint 236 | hPutBuf 237 | hPutBufNonBlocking 238 | hPutChar 239 | hPutStr 240 | hPutStrLn 241 | hReady 242 | hSeek 243 | hSetBinaryMode 244 | hSetBuffering 245 | hSetEcho 246 | hSetEncoding 247 | hSetFileSize 248 | hSetNewlineMode 249 | hSetPosn 250 | hShow 251 | hTell 252 | hWaitForInput 253 | handle 254 | handleJust 255 | head 256 | id 257 | init 258 | inits 259 | inputNL 260 | insert 261 | insertBy 262 | interact 263 | intercalate 264 | intersect 265 | intersectBy 266 | intersperse 267 | ioError 268 | isDenormalized 269 | isEOF 270 | isIEEE 271 | isInfinite 272 | isInfixOf 273 | isJust 274 | isNaN 275 | isNegativeZero 276 | isNothing 277 | isPrefixOf 278 | isSubsequenceOf 279 | isSuffixOf 280 | iterate 281 | join 282 | last 283 | latin1 284 | lcm 285 | length 286 | lex 287 | liftA 288 | liftA2 289 | liftA3 290 | liftM 291 | liftM2 292 | liftM3 293 | liftM4 294 | liftM5 295 | lines 296 | listToMaybe 297 | localeEncoding 298 | lookup 299 | many 300 | map 301 | mapAccumL 302 | mapAccumR 303 | mapAndUnzipM 304 | mapException 305 | mapM 306 | mapM_ 307 | mapMaybe 308 | mappend 309 | mask 310 | mask_ 311 | max 312 | maximum 313 | maximumBy 314 | maybe 315 | maybeToList 316 | mconcat 317 | mempty 318 | mfilter 319 | min 320 | minBound 321 | minimum 322 | minimumBy 323 | mkTextEncoding 324 | mod 325 | mplus 326 | msum 327 | mzero 328 | nativeNewline 329 | nativeNewlineMode 330 | negate 331 | noNewlineTranslation 332 | not 333 | notElem 334 | nub 335 | nubBy 336 | null 337 | odd 338 | onException 339 | openBinaryFile 340 | openBinaryTempFile 341 | openBinaryTempFileWithDefaultPermissions 342 | openFile 343 | openTempFile 344 | openTempFileWithDefaultPermissions 345 | optional 346 | or 347 | otherwise 348 | outputNL 349 | partition 350 | permutations 351 | pi 352 | pred 353 | print 354 | product 355 | properFraction 356 | pure 357 | putChar 358 | putStr 359 | putStrLn 360 | quot 361 | quotRem 362 | read 363 | readFile 364 | readIO 365 | readList 366 | readLn 367 | readParen 368 | reads 369 | readsPrec 370 | realToFrac 371 | recip 372 | rem 373 | repeat 374 | replicate 375 | replicateM 376 | replicateM_ 377 | return 378 | reverse 379 | round 380 | scaleFloat 381 | scanl 382 | scanl' 383 | scanl1 384 | scanr 385 | scanr1 386 | seq 387 | sequence 388 | sequenceA 389 | sequence_ 390 | show 391 | showChar 392 | showList 393 | showParen 394 | showString 395 | shows 396 | showsPrec 397 | significand 398 | signum 399 | sin 400 | sinh 401 | snd 402 | some 403 | sort 404 | sortBy 405 | sortOn 406 | span 407 | splitAt 408 | stderr 409 | stdin 410 | stdout 411 | stripPrefix 412 | subsequences 413 | subtract 414 | succ 415 | sum 416 | tail 417 | tails 418 | take 419 | takeWhile 420 | throw 421 | throwIO 422 | throwTo 423 | toEnum 424 | toException 425 | toInteger 426 | toRational 427 | transpose 428 | traverse 429 | truncate 430 | try 431 | tryJust 432 | uncons 433 | uncurry 434 | undefined 435 | unfoldr 436 | uninterruptibleMask 437 | uninterruptibleMask_ 438 | union 439 | unionBy 440 | universalNewlineMode 441 | unless 442 | unlines 443 | until 444 | unwords 445 | unwrapArrow 446 | unwrapMonad 447 | unzip 448 | unzip3 449 | unzip4 450 | unzip5 451 | unzip6 452 | unzip7 453 | userError 454 | utf16 455 | utf16be 456 | utf16le 457 | utf32 458 | utf32be 459 | utf32le 460 | utf8 461 | utf8_bom 462 | void 463 | when 464 | withBinaryFile 465 | withFile 466 | words 467 | writeFile 468 | zip 469 | zip3 470 | zip4 471 | zip5 472 | zip6 473 | zip7 474 | zipWith 475 | zipWith3 476 | zipWith4 477 | zipWith5 478 | zipWith6 479 | zipWith7 480 | zipWithM 481 | zipWithM_ -------------------------------------------------------------------------------- /bot/res/irssi: -------------------------------------------------------------------------------- 1 | accept 2 | action 3 | admin 4 | alias 5 | away 6 | ban 7 | beep 8 | bind 9 | cat 10 | cd 11 | channel 12 | clear 13 | completion 14 | connect 15 | ctcp 16 | cycle 17 | dcc 18 | dehilight 19 | deop 20 | devoice 21 | die 22 | disconnect 23 | echo 24 | eval 25 | exec 26 | flushbuffer 27 | foreach 28 | format 29 | hash 30 | help 31 | hilight 32 | ignore 33 | info 34 | invite 35 | ircnet 36 | ison 37 | join 38 | kick 39 | kickban 40 | kill 41 | knock 42 | knockout 43 | lastlog 44 | layout 45 | links 46 | list 47 | load 48 | log 49 | lusers 50 | map 51 | me 52 | mircdcc 53 | mode 54 | motd 55 | msg 56 | names 57 | nctcp 58 | netsplit 59 | network 60 | nick 61 | note 62 | notice 63 | notify 64 | op 65 | oper 66 | part 67 | ping 68 | query 69 | quit 70 | quote 71 | rawlog 72 | recode 73 | reconnect 74 | redraw 75 | rehash 76 | reload 77 | resize 78 | restart 79 | rmreconns 80 | rmrejoins 81 | rping 82 | save 83 | sconnect 84 | script 85 | scrollback 86 | server 87 | servlist 88 | set 89 | sethost 90 | silence 91 | squery 92 | squit 93 | stats 94 | statusbar 95 | time 96 | toggle 97 | topic 98 | trace 99 | ts 100 | unalias 101 | unban 102 | unignore 103 | unload 104 | unnotify 105 | unquery 106 | unsilence 107 | upgrade 108 | uping 109 | uptime 110 | userhost 111 | ver 112 | version 113 | voice 114 | wait 115 | wall 116 | wallops 117 | who 118 | whois 119 | whowas 120 | window 121 | -------------------------------------------------------------------------------- /bot/res/javascript: -------------------------------------------------------------------------------- 1 | arguments 2 | arguments.callee 3 | arguments.callee 4 | arguments.caller 5 | arguments.caller 6 | arguments.length 7 | arguments.length 8 | Operators 9 | Array 10 | comprehensions 11 | Array.@@iterator 12 | Array.concat 13 | Array.copyWithin 14 | Array.entries 15 | Array.every 16 | Array.fill 17 | Array.filter 18 | Array.find 19 | Array.findIndex 20 | Array.forEach 21 | Array.from 22 | Array.includes 23 | Array.indexOf 24 | Array.isArray 25 | Array.join 26 | Array.keys 27 | Array.lastIndexOf 28 | Array.length 29 | Array.map 30 | Array.observe 31 | Array.of 32 | Array.pop 33 | Array.prototype 34 | Array.push 35 | Array.reduce 36 | Array.reduceRight 37 | Array.reverse 38 | Array.shift 39 | Array.slice 40 | Array.some 41 | Array.sort 42 | Array.splice 43 | Array.toLocaleString 44 | Array.toString 45 | Array.unshift 46 | Array.values 47 | ArrayBuffer 48 | ArrayBuffer.byteLength 49 | ArrayBuffer.isView 50 | ArrayBuffer.prototype 51 | ArrayBuffer.slice 52 | ArrayBuffer.transfer 53 | functions 54 | Operators 55 | Operators 56 | block 57 | Boolean 58 | Boolean.prototype 59 | Boolean.toString 60 | Boolean.valueOf 61 | break 62 | class 63 | class 64 | Classes 65 | Operator 66 | Operators 67 | Operator 68 | const 69 | constructor 70 | continue 71 | DataView 72 | DataView.buffer 73 | DataView.byteLength 74 | DataView.byteOffset 75 | DataView.getFloat32 76 | DataView.getFloat64 77 | DataView.getInt16 78 | DataView.getInt32 79 | DataView.getInt8 80 | DataView.getUint16 81 | DataView.getUint32 82 | DataView.getUint8 83 | DataView.prototype 84 | DataView.setFloat32 85 | DataView.setFloat64 86 | DataView.setInt16 87 | DataView.setInt32 88 | DataView.setInt8 89 | DataView.setUint16 90 | DataView.setUint32 91 | DataView.setUint8 92 | Date 93 | Date.getDate 94 | Date.getDay 95 | Date.getFullYear 96 | Date.getHours 97 | Date.getMilliseconds 98 | Date.getMinutes 99 | Date.getMonth 100 | Date.getSeconds 101 | Date.getTime 102 | Date.getTimezoneOffset 103 | Date.getUTCDate 104 | Date.getUTCDay 105 | Date.getUTCFullYear 106 | Date.getUTCHours 107 | Date.getUTCMilliseconds 108 | Date.getUTCMinutes 109 | Date.getUTCMonth 110 | Date.getUTCSeconds 111 | Date.now 112 | Date.parse 113 | Date.prototype 114 | Date.setDate 115 | Date.setFullYear 116 | Date.setHours 117 | Date.setMilliseconds 118 | Date.setMinutes 119 | Date.setMonth 120 | Date.setSeconds 121 | Date.setTime 122 | Date.setUTCDate 123 | Date.setUTCFullYear 124 | Date.setUTCHours 125 | Date.setUTCMilliseconds 126 | Date.setUTCMinutes 127 | Date.setUTCMonth 128 | Date.setUTCSeconds 129 | Date.toDateString 130 | Date.toISOString 131 | Date.toJSON 132 | Date.toLocaleDateString 133 | Date.toLocaleString 134 | Date.toLocaleTimeString 135 | Date.toString 136 | Date.toTimeString 137 | Date.toUTCString 138 | Date.UTC 139 | Date.valueOf 140 | debugger 141 | decodeURI 142 | decodeURIComponent 143 | parameters 144 | delete 145 | protocol 146 | assignment 147 | do...while 148 | Empty 149 | encodeURI 150 | encodeURIComponent 151 | Error 152 | Error.message 153 | Error.name 154 | Error.prototype 155 | Error.toString 156 | eval 157 | EvalError 158 | EvalError.prototype 159 | export 160 | closures 161 | extends 162 | Float32Array 163 | float32x4 164 | Float64Array 165 | float64x2 166 | for 167 | each...in 168 | for...in 169 | for...of 170 | function 171 | function 172 | Function 173 | function* 174 | function* 175 | Function.apply 176 | Function.bind 177 | Function.call 178 | Function.constructor 179 | Function.length 180 | Function.name 181 | Function.prototype 182 | Function.toString 183 | Functions 184 | Generator 185 | comprehensions 186 | Generator.next 187 | Generator.return 188 | Generator.throw 189 | GeneratorFunction 190 | GeneratorFunction.prototype 191 | get 192 | Grouping 193 | if...else 194 | import 195 | in 196 | Infinity 197 | instanceof 198 | Int16Array 199 | int16x8 200 | Int32Array 201 | int32x4 202 | Int8Array 203 | int8x16 204 | Intl 205 | Intl.Collator 206 | Intl.Collator.compare 207 | Intl.Collator.prototype 208 | Intl.Collator.resolvedOptions 209 | Intl.Collator.supportedLocalesOf 210 | Intl.DateTimeFormat 211 | Intl.DateTimeFormat.format 212 | Intl.DateTimeFormat.prototype 213 | Intl.DateTimeFormat.resolvedOptions 214 | Intl.DateTimeFormat.supportedLocalesOf 215 | Intl.NumberFormat 216 | Intl.NumberFormat.format 217 | Intl.NumberFormat.prototype 218 | Intl.NumberFormat.resolvedOptions 219 | Intl.NumberFormat.supportedLocalesOf 220 | isFinite 221 | isNaN 222 | protocols 223 | JSON 224 | JSON.parse 225 | JSON.stringify 226 | label 227 | let 228 | grammar 229 | Operators 230 | Map 231 | Map.@@iterator 232 | Map.@@species 233 | Map.@@toStringTag 234 | Map.clear 235 | Map.delete 236 | Map.entries 237 | Map.forEach 238 | Map.get 239 | Map.has 240 | Map.keys 241 | Map.prototype 242 | Map.set 243 | Map.size 244 | Map.values 245 | Math 246 | Math.abs 247 | Math.acos 248 | Math.acosh 249 | Math.asin 250 | Math.asinh 251 | Math.atan 252 | Math.atan2 253 | Math.atanh 254 | Math.cbrt 255 | Math.ceil 256 | Math.clz32 257 | Math.cos 258 | Math.cosh 259 | Math.E 260 | Math.exp 261 | Math.expm1 262 | Math.floor 263 | Math.fround 264 | Math.hypot 265 | Math.imul 266 | Math.LN10 267 | Math.LN2 268 | Math.log 269 | Math.log10 270 | Math.LOG10E 271 | Math.log1p 272 | Math.log2 273 | Math.LOG2E 274 | Math.max 275 | Math.min 276 | Math.PI 277 | Math.pow 278 | Math.random 279 | Math.round 280 | Math.sign 281 | Math.sin 282 | Math.sinh 283 | Math.sqrt 284 | Math.SQRT1_2 285 | Math.SQRT2 286 | Math.tan 287 | Math.tanh 288 | Math.trunc 289 | definitions 290 | NaN 291 | new 292 | new.target 293 | null 294 | Number 295 | Number.EPSILON 296 | Number.isFinite 297 | Number.isInteger 298 | Number.isNaN 299 | Number.isSafeInteger 300 | Number.MAX_SAFE_INTEGER 301 | Number.MAX_VALUE 302 | Number.MIN_SAFE_INTEGER 303 | Number.MIN_VALUE 304 | Number.NaN 305 | Number.NEGATIVE_INFINITY 306 | Number.parseFloat 307 | Number.parseInt 308 | Number.POSITIVE_INFINITY 309 | Number.prototype 310 | Number.toExponential 311 | Number.toFixed 312 | Number.toInteger 313 | Number.toLocaleString 314 | Number.toPrecision 315 | Number.toString 316 | Number.valueOf 317 | Object 318 | initializer 319 | Object.assign 320 | Object.constructor 321 | Object.create 322 | Object.defineProperties 323 | Object.defineProperty 324 | Object.freeze 325 | Object.getOwnPropertyDescriptor 326 | Object.getOwnPropertyNames 327 | Object.getOwnPropertySymbols 328 | Object.getPrototypeOf 329 | Object.hasOwnProperty 330 | Object.is 331 | Object.isExtensible 332 | Object.isFrozen 333 | Object.isPrototypeOf 334 | Object.isSealed 335 | Object.keys 336 | Object.observe 337 | Object.preventExtensions 338 | Object.propertyIsEnumerable 339 | Object.proto 340 | Object.prototype 341 | Object.seal 342 | Object.setPrototypeOf 343 | Object.toLocaleString 344 | Object.toString 345 | Object.unobserve 346 | Object.unwatch 347 | Object.valueOf 348 | Object.watch 349 | Object.__defineGetter__ 350 | Object.__defineSetter__ 351 | Object.__lookupGetter__ 352 | Object.__lookupSetter__ 353 | Object.__proto__ 354 | Precedence 355 | ParallelArray 356 | parseFloat 357 | parseInt 358 | Promise 359 | Promise.all 360 | Promise.catch 361 | Promise.prototype 362 | Promise.race 363 | Promise.reject 364 | Promise.resolve 365 | Promise.then 366 | Accessors 367 | Proxy.handler 368 | Proxy.handler.apply 369 | Proxy.handler.construct 370 | Proxy.handler.defineProperty 371 | Proxy.handler.deleteProperty 372 | Proxy.handler.enumerate 373 | Proxy.handler.get 374 | Proxy.handler.getOwnPropertyDescriptor 375 | Proxy.handler.getPrototypeOf 376 | Proxy.handler.has 377 | Proxy.handler.isExtensible 378 | Proxy.handler.ownKeys 379 | Proxy.handler.preventExtensions 380 | Proxy.handler.set 381 | Proxy.handler.setPrototypeOf 382 | RangeError 383 | RangeError.prototype 384 | ReferenceError 385 | ReferenceError.prototype 386 | Reflect 387 | RegExp 388 | RegExp.exec 389 | RegExp.flags 390 | RegExp.global 391 | RegExp.ignoreCase 392 | RegExp.lastIndex 393 | RegExp.multiline 394 | RegExp.prototype 395 | RegExp.source 396 | RegExp.sticky 397 | RegExp.test 398 | RegExp.toString 399 | RegExp.unicode 400 | parameters 401 | return 402 | Set 403 | set 404 | Set.@@iterator 405 | Set.@@species 406 | Set.add 407 | Set.clear 408 | Set.delete 409 | Set.entries 410 | Set.forEach 411 | Set.has 412 | Set.keys 413 | Set.prototype 414 | Set.size 415 | Set.values 416 | SIMD 417 | SIMD.abs 418 | SIMD.add 419 | SIMD.and 420 | SIMD.bool 421 | SIMD.check 422 | SIMD.div 423 | SIMD.equal 424 | SIMD.extractLane 425 | SIMD.fromFloat32x4 426 | SIMD.fromFloat32x4Bits 427 | SIMD.fromFloat64x2 428 | SIMD.fromFloat64x2Bits 429 | SIMD.fromInt16x8Bits 430 | SIMD.fromInt32x4 431 | SIMD.fromInt32x4Bits 432 | SIMD.fromInt8x16Bits 433 | SIMD.greaterThan 434 | SIMD.greaterThanOrEqual 435 | SIMD.lessThan 436 | SIMD.lessThanOrEqual 437 | SIMD.load 438 | SIMD.max 439 | SIMD.maxNum 440 | SIMD.min 441 | SIMD.minNum 442 | SIMD.mul 443 | SIMD.neg 444 | SIMD.not 445 | SIMD.notEqual 446 | SIMD.or 447 | SIMD.reciprocalApproximation 448 | SIMD.reciprocalSqrtApproximation 449 | SIMD.replaceLane 450 | SIMD.select 451 | SIMD.selectBits 452 | SIMD.shiftLeftByScalar 453 | SIMD.shiftRightArithmeticByScalar 454 | SIMD.shiftRightLogicalByScalar 455 | SIMD.shuffle 456 | SIMD.splat 457 | SIMD.sqrt 458 | SIMD.store 459 | SIMD.sub 460 | SIMD.swizzle 461 | SIMD.xor 462 | operator 463 | static 464 | mode 465 | String 466 | Operators 467 | String.@@iterator 468 | String.anchor 469 | String.charAt 470 | String.charCodeAt 471 | String.codePointAt 472 | String.concat 473 | String.endsWith 474 | String.fromCharCode 475 | String.fromCodePoint 476 | String.includes 477 | String.indexOf 478 | String.lastIndexOf 479 | String.length 480 | String.link 481 | String.localeCompare 482 | String.match 483 | String.normalize 484 | String.prototype 485 | String.quote 486 | String.raw 487 | String.repeat 488 | String.replace 489 | String.search 490 | String.slice 491 | String.split 492 | String.startsWith 493 | String.substr 494 | String.substring 495 | String.toLocaleLowerCase 496 | String.toLocaleUpperCase 497 | String.toLowerCase 498 | String.toString 499 | String.toUpperCase 500 | String.trim 501 | String.valueOf 502 | super 503 | switch 504 | Symbol 505 | Symbol.for 506 | Symbol.iterator 507 | Symbol.keyFor 508 | Symbol.match 509 | Symbol.prototype 510 | Symbol.species 511 | Symbol.toString 512 | Symbol.valueOf 513 | SyntaxError 514 | SyntaxError.prototype 515 | strings 516 | this 517 | throw 518 | try...catch 519 | TypedArray 520 | TypedArray.@@iterator 521 | TypedArray.buffer 522 | TypedArray.byteLength 523 | TypedArray.byteOffset 524 | TypedArray.BYTES_PER_ELEMENT 525 | TypedArray.copyWithin 526 | TypedArray.entries 527 | TypedArray.every 528 | TypedArray.fill 529 | TypedArray.filter 530 | TypedArray.find 531 | TypedArray.findIndex 532 | TypedArray.forEach 533 | TypedArray.from 534 | TypedArray.includes 535 | TypedArray.indexOf 536 | TypedArray.join 537 | TypedArray.keys 538 | TypedArray.lastIndexOf 539 | TypedArray.length 540 | TypedArray.map 541 | TypedArray.name 542 | TypedArray.of 543 | TypedArray.prototype 544 | TypedArray.reduce 545 | TypedArray.reduceRight 546 | TypedArray.reverse 547 | TypedArray.set 548 | TypedArray.slice 549 | TypedArray.some 550 | TypedArray.subarray 551 | TypedArray.values 552 | TypeError 553 | TypeError.prototype 554 | typeof 555 | Uint16Array 556 | Uint32Array 557 | Uint8Array 558 | Uint8ClampedArray 559 | undefined 560 | URIError 561 | URIError.prototype 562 | var 563 | void 564 | WeakMap 565 | WeakMap.delete 566 | WeakMap.get 567 | WeakMap.has 568 | WeakMap.prototype 569 | WeakMap.set 570 | WeakSet 571 | WeakSet.add 572 | WeakSet.delete 573 | WeakSet.has 574 | WeakSet.prototype 575 | while 576 | with 577 | yield 578 | yield* 579 | -------------------------------------------------------------------------------- /bot/res/perl: -------------------------------------------------------------------------------- 1 | $ACCUMULATOR 2 | $ARG 3 | $ARGV 4 | $BASETIME 5 | $CHILD_ERROR 6 | $COMPILING 7 | $DEBUGGING 8 | $EFFECTIVE_GROUP_ID 9 | $EFFECTIVE_USER_ID 10 | $EGID 11 | $ERRNO 12 | $EUID 13 | $EVAL_ERROR 14 | $EXCEPTIONS_BEING_CAUGHT 15 | $EXECUTABLE_NAME 16 | $EXTENDED_OS_ERROR 17 | $FORMAT_FORMFEED 18 | $FORMAT_LINES_LEFT 19 | $FORMAT_LINES_PER_PAGE 20 | $FORMAT_LINE_BREAK_CHARACTERS 21 | $FORMAT_NAME 22 | $FORMAT_PAGE_NUMBER 23 | $FORMAT_TOP_NAME 24 | $GID 25 | $INPLACE_EDIT 26 | $INPUT_LINE_NUMBER 27 | $INPUT_RECORD_SEPARATOR 28 | $LAST_MATCH_END 29 | $LAST_PAREN_MATCH 30 | $LAST_REGEXP_CODE_RESULT 31 | $LIST_SEPARATOR 32 | $MATCH 33 | $MULTILINE_MATCHING 34 | $NR 35 | $OFMT 36 | $OFS 37 | $ORS 38 | $OSNAME 39 | $OS_ERROR 40 | $OUTPUT_AUTO_FLUSH 41 | $OUTPUT_FIELD_SEPARATOR 42 | $OUTPUT_RECORD_SEPARATOR 43 | $PERLDB 44 | $PERL_VERSION 45 | $PID 46 | $POSTMATCH 47 | $PREMATCH 48 | $PROCESS_ID 49 | $PROGRAM_NAME 50 | $REAL_GROUP_ID 51 | $REAL_USER_ID 52 | $RS 53 | $SUBSCRIPT_SEPARATOR 54 | $SUBSEP 55 | $SYSTEM_FD_MAX 56 | $UID 57 | $WARNING 58 | $^CHILD_ERROR_NATIVE 59 | $^ENCODING 60 | $^OPEN 61 | $^RE_DEBUG_FLAGS 62 | $^RE_TRIE_MAXBUF 63 | $^TAINT 64 | $^UNICODE 65 | $^UTF8LOCALE 66 | $^WARNING_BITS 67 | $^WIDE_SYSTEM_CALLS 68 | %ENV 69 | %INC 70 | %OVERLOAD 71 | %SIG 72 | @ARGV 73 | @INC 74 | @LAST_MATCH_START 75 | ARGV 76 | ARGVOUT 77 | AUTOLOAD 78 | BEGIN 79 | CHECK 80 | CORE 81 | DESTROY 82 | END 83 | INIT 84 | STDERR 85 | STDIN 86 | STDOUT 87 | UNITCHECK 88 | __DATA__ 89 | __END__ 90 | __FILE__ 91 | __LINE__ 92 | __PACKAGE__ 93 | abs 94 | accept 95 | alarm 96 | and 97 | atan2 98 | bind 99 | binmode 100 | bless 101 | break 102 | caller 103 | chdir 104 | chmod 105 | chomp 106 | chop 107 | chown 108 | chr 109 | chroot 110 | close 111 | closedir 112 | cmp 113 | connect 114 | continue 115 | cos 116 | crypt 117 | dbmclose 118 | dbmopen 119 | defined 120 | delete 121 | die 122 | dump 123 | each 124 | else 125 | elsif 126 | endgrent 127 | endhostent 128 | endnetent 129 | endprotoent 130 | endpwent 131 | endservent 132 | eof 133 | eval 134 | exec 135 | exists 136 | exit 137 | exp 138 | fcntl 139 | fileno 140 | flock 141 | for 142 | foreach 143 | fork 144 | format 145 | formline 146 | getc 147 | getgrent 148 | getgrgid 149 | getgrnam 150 | gethostbyaddr 151 | gethostbyname 152 | gethostent 153 | getlogin 154 | getnetbyaddr 155 | getnetbyname 156 | getnetent 157 | getpeername 158 | getpgrp 159 | getppid 160 | getpriority 161 | getprotobyname 162 | getprotobynumber 163 | getprotoent 164 | getpwent 165 | getpwnam 166 | getpwuid 167 | getservbyname 168 | getservbyport 169 | getservent 170 | getsockname 171 | getsockopt 172 | glob 173 | gmtime 174 | goto 175 | grep 176 | hex 177 | index 178 | int 179 | ioctl 180 | join 181 | keys 182 | kill 183 | last 184 | lcfirst 185 | length 186 | link 187 | listen 188 | local 189 | localtime 190 | lock 191 | log 192 | lstat 193 | map 194 | mkdir 195 | msgctl 196 | msgget 197 | msgrcv 198 | msgsnd 199 | next 200 | not 201 | oct 202 | open 203 | opendir 204 | ord 205 | our 206 | pack 207 | package 208 | pipe 209 | pop 210 | pos 211 | print 212 | printf 213 | prototype 214 | push 215 | quotemeta 216 | rand 217 | read 218 | read 219 | readdir 220 | readline 221 | readpipe 222 | recv 223 | redo 224 | ref 225 | rename 226 | require 227 | reset 228 | return 229 | reverse 230 | rewinddir 231 | rindex 232 | rmdir 233 | say 234 | scalar 235 | seek 236 | seekdir 237 | select 238 | semctl 239 | semget 240 | semop 241 | send 242 | setgrent 243 | sethostent 244 | setnetent 245 | setpgrp 246 | setpriority 247 | setprotoent 248 | setpwent 249 | setservent 250 | setsockopt 251 | shift 252 | shmctl 253 | shmget 254 | shmread 255 | shmwrite 256 | shutdown 257 | sin 258 | sleep 259 | socket 260 | socketpair 261 | sort 262 | splice 263 | split 264 | sprintf 265 | sqrt 266 | srand 267 | stat 268 | state 269 | study 270 | sub 271 | substr 272 | gensym 273 | syscall 274 | sysopen 275 | sysread 276 | sysseek 277 | system 278 | syswrite 279 | tell 280 | telldir 281 | tie 282 | tied 283 | time 284 | times 285 | truncate 286 | ucfirst 287 | umask 288 | undef 289 | unless 290 | unlink 291 | unpack 292 | unshift 293 | untie 294 | until 295 | use 296 | utime 297 | values 298 | vec 299 | wait 300 | waitpid 301 | wantarray 302 | warn 303 | while 304 | write 305 | xor 306 | -------------------------------------------------------------------------------- /bot/res/pokemon: -------------------------------------------------------------------------------- 1 | bulbasaur 2 | ivysaur 3 | venusaur 4 | charmander 5 | charmeleon 6 | charizard 7 | squirtle 8 | wartortle 9 | blastoise 10 | caterpie 11 | metapod 12 | butterfree 13 | weedle 14 | kakuna 15 | beedrill 16 | pidgey 17 | pidgeotto 18 | pidgeot 19 | rattata 20 | raticate 21 | spearow 22 | fearow 23 | ekans 24 | arbok 25 | pikachu 26 | raichu 27 | sandshrew 28 | sandslash 29 | nidoranf 30 | nidorina 31 | nidoqueen 32 | nidoranm 33 | nidorino 34 | nidoking 35 | clefairy 36 | clefable 37 | vulpix 38 | ninetales 39 | jigglypuff 40 | wigglytuff 41 | zubat 42 | golbat 43 | oddish 44 | gloom 45 | vileplume 46 | paras 47 | parasect 48 | venonat 49 | venomoth 50 | diglett 51 | dugtrio 52 | meowth 53 | persian 54 | psyduck 55 | golduck 56 | mankey 57 | primeape 58 | growlithe 59 | arcanine 60 | poliwag 61 | poliwhirl 62 | poliwrath 63 | abra 64 | kadabra 65 | alakazam 66 | machop 67 | machoke 68 | machamp 69 | bellsprout 70 | weepinbell 71 | victreebel 72 | tentacool 73 | tentacruel 74 | geodude 75 | graveler 76 | golem 77 | ponyta 78 | rapidash 79 | slowpoke 80 | slowbro 81 | magnemite 82 | magneton 83 | farfetchd 84 | doduo 85 | dodrio 86 | seel 87 | dewgong 88 | grimer 89 | muk 90 | shellder 91 | cloyster 92 | gastly 93 | haunter 94 | gengar 95 | onix 96 | drowzee 97 | hypno 98 | krabby 99 | kingler 100 | voltorb 101 | electrode 102 | exeggcute 103 | exeggutor 104 | cubone 105 | marowak 106 | hitmonlee 107 | hitmonchan 108 | lickitung 109 | koffing 110 | weezing 111 | rhyhorn 112 | rhydon 113 | chansey 114 | tangela 115 | kangaskhan 116 | horsea 117 | seadra 118 | goldeen 119 | seaking 120 | staryu 121 | starmie 122 | mr-mime 123 | scyther 124 | jynx 125 | electabuzz 126 | magmar 127 | pinsir 128 | tauros 129 | magikarp 130 | gyarados 131 | lapras 132 | ditto 133 | eevee 134 | vaporeon 135 | jolteon 136 | flareon 137 | porygon 138 | omanyte 139 | omastar 140 | kabuto 141 | kabutops 142 | aerodactyl 143 | snorlax 144 | articuno 145 | zapdos 146 | moltres 147 | dratini 148 | dragonair 149 | dragonite 150 | mewtwo 151 | mew 152 | chikorita 153 | bayleef 154 | meganium 155 | cyndaquil 156 | quilava 157 | typhlosion 158 | totodile 159 | croconaw 160 | feraligatr 161 | sentret 162 | furret 163 | hoothoot 164 | noctowl 165 | ledyba 166 | ledian 167 | spinarak 168 | ariados 169 | crobat 170 | chinchou 171 | lanturn 172 | pichu 173 | cleffa 174 | igglybuff 175 | togepi 176 | togetic 177 | natu 178 | xatu 179 | mareep 180 | flaaffy 181 | ampharos 182 | bellossom 183 | marill 184 | azumarill 185 | sudowoodo 186 | politoed 187 | hoppip 188 | skiploom 189 | jumpluff 190 | aipom 191 | sunkern 192 | sunflora 193 | yanma 194 | wooper 195 | quagsire 196 | espeon 197 | umbreon 198 | murkrow 199 | slowking 200 | misdreavus 201 | unown 202 | wobbuffet 203 | girafarig 204 | pineco 205 | forretress 206 | dunsparce 207 | gligar 208 | steelix 209 | snubbull 210 | granbull 211 | qwilfish 212 | scizor 213 | shuckle 214 | heracross 215 | sneasel 216 | teddiursa 217 | ursaring 218 | slugma 219 | magcargo 220 | swinub 221 | piloswine 222 | corsola 223 | remoraid 224 | octillery 225 | delibird 226 | mantine 227 | skarmory 228 | houndour 229 | houndoom 230 | kingdra 231 | phanpy 232 | donphan 233 | porygon2 234 | stantler 235 | smeargle 236 | tyrogue 237 | hitmontop 238 | smoochum 239 | elekid 240 | magby 241 | miltank 242 | blissey 243 | raikou 244 | entei 245 | suicune 246 | larvitar 247 | pupitar 248 | tyranitar 249 | lugia 250 | ho-oh 251 | celebi 252 | treecko 253 | grovyle 254 | sceptile 255 | torchic 256 | combusken 257 | blaziken 258 | mudkip 259 | marshtomp 260 | swampert 261 | poochyena 262 | mightyena 263 | zigzagoon 264 | linoone 265 | wurmple 266 | silcoon 267 | beautifly 268 | cascoon 269 | dustox 270 | lotad 271 | lombre 272 | ludicolo 273 | seedot 274 | nuzleaf 275 | shiftry 276 | taillow 277 | swellow 278 | wingull 279 | pelipper 280 | ralts 281 | kirlia 282 | gardevoir 283 | surskit 284 | masquerain 285 | shroomish 286 | breloom 287 | slakoth 288 | vigoroth 289 | slaking 290 | nincada 291 | ninjask 292 | shedinja 293 | whismur 294 | loudred 295 | exploud 296 | makuhita 297 | hariyama 298 | azurill 299 | nosepass 300 | skitty 301 | delcatty 302 | sableye 303 | mawile 304 | aron 305 | lairon 306 | aggron 307 | meditite 308 | medicham 309 | electrike 310 | manectric 311 | plusle 312 | minun 313 | volbeat 314 | illumise 315 | roselia 316 | gulpin 317 | swalot 318 | carvanha 319 | sharpedo 320 | wailmer 321 | wailord 322 | numel 323 | camerupt 324 | torkoal 325 | spoink 326 | grumpig 327 | spinda 328 | trapinch 329 | vibrava 330 | flygon 331 | cacnea 332 | cacturne 333 | swablu 334 | altaria 335 | zangoose 336 | seviper 337 | lunatone 338 | solrock 339 | barboach 340 | whiscash 341 | corphish 342 | crawdaunt 343 | baltoy 344 | claydol 345 | lileep 346 | cradily 347 | anorith 348 | armaldo 349 | feebas 350 | milotic 351 | castform 352 | kecleon 353 | shuppet 354 | banette 355 | duskull 356 | dusclops 357 | tropius 358 | chimecho 359 | absol 360 | wynaut 361 | snorunt 362 | glalie 363 | spheal 364 | sealeo 365 | walrein 366 | clamperl 367 | huntail 368 | gorebyss 369 | relicanth 370 | luvdisc 371 | bagon 372 | shelgon 373 | salamence 374 | beldum 375 | metang 376 | metagross 377 | regirock 378 | regice 379 | registeel 380 | latias 381 | latios 382 | kyogre 383 | groudon 384 | rayquaza 385 | jirachi 386 | deoxys 387 | turtwig 388 | grotle 389 | torterra 390 | chimchar 391 | monferno 392 | infernape 393 | piplup 394 | prinplup 395 | empoleon 396 | starly 397 | staravia 398 | staraptor 399 | bidoof 400 | bibarel 401 | kricketot 402 | kricketune 403 | shinx 404 | luxio 405 | luxray 406 | budew 407 | roserade 408 | cranidos 409 | rampardos 410 | shieldon 411 | bastiodon 412 | burmy 413 | wormadam 414 | mothim 415 | combee 416 | vespiquen 417 | pachirisu 418 | buizel 419 | floatzel 420 | cherubi 421 | cherrim 422 | shellos 423 | gastrodon 424 | ambipom 425 | drifloon 426 | drifblim 427 | buneary 428 | lopunny 429 | mismagius 430 | honchkrow 431 | glameow 432 | purugly 433 | chingling 434 | stunky 435 | skuntank 436 | bronzor 437 | bronzong 438 | bonsly 439 | mime-jr 440 | happiny 441 | chatot 442 | spiritomb 443 | gible 444 | gabite 445 | garchomp 446 | munchlax 447 | riolu 448 | lucario 449 | hippopotas 450 | hippowdon 451 | skorupi 452 | drapion 453 | croagunk 454 | toxicroak 455 | carnivine 456 | finneon 457 | lumineon 458 | mantyke 459 | snover 460 | abomasnow 461 | weavile 462 | magnezone 463 | lickilicky 464 | rhyperior 465 | tangrowth 466 | electivire 467 | magmortar 468 | togekiss 469 | yanmega 470 | leafeon 471 | glaceon 472 | gliscor 473 | mamoswine 474 | porygon-z 475 | gallade 476 | probopass 477 | dusknoir 478 | froslass 479 | rotom 480 | uxie 481 | mesprit 482 | azelf 483 | dialga 484 | palkia 485 | heatran 486 | regigigas 487 | giratina 488 | cresselia 489 | phione 490 | manaphy 491 | darkrai 492 | shaymin 493 | arceus 494 | victini 495 | snivy 496 | servine 497 | serperior 498 | tepig 499 | pignite 500 | emboar 501 | oshawott 502 | dewott 503 | samurott 504 | patrat 505 | watchog 506 | lillipup 507 | herdier 508 | stoutland 509 | purrloin 510 | liepard 511 | pansage 512 | simisage 513 | pansear 514 | simisear 515 | panpour 516 | simipour 517 | munna 518 | musharna 519 | pidove 520 | tranquill 521 | unfezant 522 | blitzle 523 | zebstrika 524 | roggenrola 525 | boldore 526 | gigalith 527 | woobat 528 | swoobat 529 | drilbur 530 | excadrill 531 | audino 532 | timburr 533 | gurdurr 534 | conkeldurr 535 | tympole 536 | palpitoad 537 | seismitoad 538 | throh 539 | sawk 540 | sewaddle 541 | swadloon 542 | leavanny 543 | venipede 544 | whirlipede 545 | scolipede 546 | cottonee 547 | whimsicott 548 | petilil 549 | lilligant 550 | basculin 551 | sandile 552 | krokorok 553 | krookodile 554 | darumaka 555 | darmanitan 556 | maractus 557 | dwebble 558 | crustle 559 | scraggy 560 | scrafty 561 | sigilyph 562 | yamask 563 | cofagrigus 564 | tirtouga 565 | carracosta 566 | archen 567 | archeops 568 | trubbish 569 | garbodor 570 | zorua 571 | zoroark 572 | minccino 573 | cinccino 574 | gothita 575 | gothorita 576 | gothitelle 577 | solosis 578 | duosion 579 | reuniclus 580 | ducklett 581 | swanna 582 | vanillite 583 | vanillish 584 | vanilluxe 585 | deerling 586 | sawsbuck 587 | emolga 588 | karrablast 589 | escavalier 590 | foongus 591 | amoonguss 592 | frillish 593 | jellicent 594 | alomomola 595 | joltik 596 | galvantula 597 | ferroseed 598 | ferrothorn 599 | klink 600 | klang 601 | klinklang 602 | tynamo 603 | eelektrik 604 | eelektross 605 | elgyem 606 | beheeyem 607 | litwick 608 | lampent 609 | chandelure 610 | axew 611 | fraxure 612 | haxorus 613 | cubchoo 614 | beartic 615 | cryogonal 616 | shelmet 617 | accelgor 618 | stunfisk 619 | mienfoo 620 | mienshao 621 | druddigon 622 | golett 623 | golurk 624 | pawniard 625 | bisharp 626 | bouffalant 627 | rufflet 628 | braviary 629 | vullaby 630 | mandibuzz 631 | heatmor 632 | durant 633 | deino 634 | zweilous 635 | hydreigon 636 | larvesta 637 | volcarona 638 | cobalion 639 | terrakion 640 | virizion 641 | tornadus 642 | thundurus 643 | reshiram 644 | zekrom 645 | landorus 646 | kyurem 647 | keldeo 648 | meloetta 649 | genesect 650 | chespin 651 | quilladin 652 | chesnaught 653 | fennekin 654 | braixen 655 | delphox 656 | froakie 657 | frogadier 658 | greninja 659 | bunnelby 660 | diggersby 661 | fletchling 662 | fletchinder 663 | talonflame 664 | scatterbug 665 | spewpa 666 | vivillon 667 | litleo 668 | pyroar 669 | flabebe 670 | floette 671 | florges 672 | skiddo 673 | gogoat 674 | pancham 675 | pangoro 676 | furfrou 677 | espurr 678 | meowstic 679 | honedge 680 | doublade 681 | aegislash 682 | spritzee 683 | aromatisse 684 | swirlix 685 | slurpuff 686 | inkay 687 | malamar 688 | binacle 689 | barbaracle 690 | skrelp 691 | dragalge 692 | clauncher 693 | clawitzer 694 | helioptile 695 | heliolisk 696 | tyrunt 697 | tyrantrum 698 | amaura 699 | aurorus 700 | sylveon 701 | hawlucha 702 | dedenne 703 | carbink 704 | goomy 705 | sliggoo 706 | goodra 707 | klefki 708 | phantump 709 | trevenant 710 | pumpkaboo 711 | gourgeist 712 | bergmite 713 | avalugg 714 | noibat 715 | noivern 716 | xerneas 717 | yveltal 718 | zygarde 719 | diancie 720 | hoopa 721 | volcanion 722 | -------------------------------------------------------------------------------- /bot/res/wordlist: -------------------------------------------------------------------------------- 1 | ability 2 | abroad 3 | abuse 4 | access 5 | accident 6 | account 7 | act 8 | action 9 | active 10 | activity 11 | actor 12 | ad 13 | addition 14 | address 15 | administration 16 | adult 17 | advance 18 | advantage 19 | advertising 20 | advice 21 | affair 22 | affect 23 | afternoon 24 | age 25 | agency 26 | agent 27 | agreement 28 | air 29 | airline 30 | airport 31 | alarm 32 | alcohol 33 | alternative 34 | ambition 35 | amount 36 | analysis 37 | analyst 38 | anger 39 | angle 40 | animal 41 | annual 42 | answer 43 | anxiety 44 | anybody 45 | anything 46 | anywhere 47 | apartment 48 | appeal 49 | appearance 50 | apple 51 | application 52 | appointment 53 | area 54 | argument 55 | arm 56 | army 57 | arrival 58 | art 59 | article 60 | aside 61 | ask 62 | aspect 63 | assignment 64 | assist 65 | assistance 66 | assistant 67 | associate 68 | association 69 | assumption 70 | atmosphere 71 | attack 72 | attempt 73 | attention 74 | attitude 75 | audience 76 | author 77 | average 78 | award 79 | awareness 80 | baby 81 | back 82 | background 83 | bad 84 | bag 85 | bake 86 | balance 87 | ball 88 | band 89 | bank 90 | bar 91 | base 92 | baseball 93 | basis 94 | basket 95 | bat 96 | bath 97 | bathroom 98 | battle 99 | beach 100 | bear 101 | beat 102 | beautiful 103 | bed 104 | bedroom 105 | beer 106 | beginning 107 | being 108 | bell 109 | belt 110 | bench 111 | bend 112 | benefit 113 | bet 114 | beyond 115 | bicycle 116 | bid 117 | big 118 | bike 119 | bill 120 | bird 121 | birth 122 | birthday 123 | bit 124 | bite 125 | bitter 126 | black 127 | blame 128 | blank 129 | blind 130 | block 131 | blood 132 | blow 133 | blue 134 | board 135 | boat 136 | body 137 | bone 138 | bonus 139 | book 140 | boot 141 | border 142 | boss 143 | bother 144 | bottle 145 | bottom 146 | bowl 147 | box 148 | boy 149 | boyfriend 150 | brain 151 | branch 152 | brave 153 | bread 154 | break 155 | breakfast 156 | breast 157 | breath 158 | brick 159 | bridge 160 | brief 161 | brilliant 162 | broad 163 | brother 164 | brown 165 | brush 166 | buddy 167 | budget 168 | bug 169 | building 170 | bunch 171 | burn 172 | bus 173 | business 174 | button 175 | buy 176 | buyer 177 | cabinet 178 | cable 179 | cake 180 | calendar 181 | call 182 | calm 183 | camera 184 | camp 185 | campaign 186 | can 187 | cancel 188 | cancer 189 | candidate 190 | candle 191 | candy 192 | cap 193 | capital 194 | car 195 | card 196 | care 197 | career 198 | carpet 199 | carry 200 | case 201 | cash 202 | cat 203 | catch 204 | category 205 | cause 206 | celebration 207 | cell 208 | chain 209 | chair 210 | challenge 211 | champion 212 | championship 213 | chance 214 | change 215 | channel 216 | chapter 217 | character 218 | charge 219 | charity 220 | chart 221 | check 222 | cheek 223 | chemical 224 | chemistry 225 | chest 226 | chicken 227 | child 228 | childhood 229 | chip 230 | chocolate 231 | choice 232 | church 233 | cigarette 234 | city 235 | claim 236 | class 237 | classic 238 | classroom 239 | clerk 240 | click 241 | client 242 | climate 243 | clock 244 | closet 245 | clothes 246 | cloud 247 | club 248 | clue 249 | coach 250 | coast 251 | coat 252 | code 253 | coffee 254 | cold 255 | collar 256 | collection 257 | college 258 | combination 259 | combine 260 | comfort 261 | comfortable 262 | command 263 | comment 264 | commercial 265 | commission 266 | committee 267 | common 268 | communication 269 | community 270 | company 271 | comparison 272 | competition 273 | complaint 274 | complex 275 | computer 276 | concentrate 277 | concept 278 | concern 279 | concert 280 | conclusion 281 | condition 282 | conference 283 | confidence 284 | conflict 285 | confusion 286 | connection 287 | consequence 288 | consideration 289 | consist 290 | constant 291 | construction 292 | contact 293 | contest 294 | context 295 | contract 296 | contribution 297 | control 298 | conversation 299 | convert 300 | cook 301 | cookie 302 | copy 303 | corner 304 | cost 305 | count 306 | counter 307 | country 308 | county 309 | couple 310 | courage 311 | course 312 | court 313 | cousin 314 | cover 315 | cow 316 | crack 317 | craft 318 | crash 319 | crazy 320 | cream 321 | creative 322 | credit 323 | crew 324 | criticism 325 | cross 326 | cry 327 | culture 328 | cup 329 | currency 330 | current 331 | curve 332 | customer 333 | cut 334 | cycle 335 | dad 336 | damage 337 | dance 338 | dare 339 | dark 340 | data 341 | database 342 | date 343 | daughter 344 | day 345 | dead 346 | deal 347 | dealer 348 | dear 349 | death 350 | debate 351 | debt 352 | decision 353 | deep 354 | definition 355 | degree 356 | delay 357 | delivery 358 | demand 359 | department 360 | departure 361 | dependent 362 | deposit 363 | depression 364 | depth 365 | description 366 | design 367 | designer 368 | desire 369 | desk 370 | detail 371 | development 372 | device 373 | devil 374 | diamond 375 | diet 376 | difference 377 | difficulty 378 | dig 379 | dimension 380 | dinner 381 | direction 382 | director 383 | dirt 384 | disaster 385 | discipline 386 | discount 387 | discussion 388 | disease 389 | dish 390 | disk 391 | display 392 | distance 393 | distribution 394 | district 395 | divide 396 | doctor 397 | document 398 | dog 399 | door 400 | dot 401 | double 402 | doubt 403 | draft 404 | drag 405 | drama 406 | draw 407 | drawer 408 | drawing 409 | dream 410 | dress 411 | drink 412 | drive 413 | driver 414 | drop 415 | drunk 416 | due 417 | dump 418 | dust 419 | duty 420 | ear 421 | earth 422 | ease 423 | east 424 | eat 425 | economics 426 | economy 427 | edge 428 | editor 429 | education 430 | effect 431 | effective 432 | efficiency 433 | effort 434 | egg 435 | election 436 | elevator 437 | emergency 438 | emotion 439 | emphasis 440 | employ 441 | employee 442 | employer 443 | employment 444 | end 445 | energy 446 | engine 447 | engineer 448 | engineering 449 | entertainment 450 | enthusiasm 451 | entrance 452 | entry 453 | environment 454 | equal 455 | equipment 456 | equivalent 457 | error 458 | escape 459 | essay 460 | establishment 461 | estate 462 | estimate 463 | evening 464 | event 465 | evidence 466 | exam 467 | examination 468 | example 469 | exchange 470 | excitement 471 | excuse 472 | exercise 473 | exit 474 | experience 475 | expert 476 | explanation 477 | expression 478 | extension 479 | extent 480 | external 481 | extreme 482 | eye 483 | face 484 | fact 485 | factor 486 | fail 487 | failure 488 | fall 489 | familiar 490 | family 491 | fan 492 | farm 493 | farmer 494 | fat 495 | father 496 | fault 497 | fear 498 | feature 499 | fee 500 | feed 501 | feedback 502 | feel 503 | feeling 504 | female 505 | few 506 | field 507 | fight 508 | figure 509 | file 510 | fill 511 | film 512 | final 513 | finance 514 | finding 515 | finger 516 | finish 517 | fire 518 | fish 519 | fishing 520 | fix 521 | flight 522 | floor 523 | flow 524 | flower 525 | fly 526 | focus 527 | fold 528 | following 529 | food 530 | foot 531 | football 532 | force 533 | forever 534 | form 535 | formal 536 | fortune 537 | foundation 538 | frame 539 | freedom 540 | friend 541 | friendship 542 | front 543 | fruit 544 | fuel 545 | fun 546 | function 547 | funeral 548 | funny 549 | future 550 | gain 551 | game 552 | gap 553 | garage 554 | garbage 555 | garden 556 | gas 557 | gate 558 | gather 559 | gear 560 | gene 561 | general 562 | gift 563 | girl 564 | girlfriend 565 | give 566 | glad 567 | glass 568 | glove 569 | go 570 | goal 571 | god 572 | gold 573 | golf 574 | good 575 | government 576 | grab 577 | grade 578 | grand 579 | grandfather 580 | grandmother 581 | grass 582 | great 583 | green 584 | grocery 585 | ground 586 | group 587 | growth 588 | guarantee 589 | guard 590 | guess 591 | guest 592 | guidance 593 | guide 594 | guitar 595 | guy 596 | habit 597 | hair 598 | half 599 | hall 600 | hand 601 | handle 602 | hang 603 | harm 604 | hat 605 | hate 606 | head 607 | health 608 | hearing 609 | heart 610 | heat 611 | heavy 612 | height 613 | hell 614 | hello 615 | help 616 | hide 617 | high 618 | highlight 619 | highway 620 | hire 621 | historian 622 | history 623 | hit 624 | hold 625 | hole 626 | holiday 627 | home 628 | homework 629 | honey 630 | hook 631 | hope 632 | horror 633 | horse 634 | hospital 635 | host 636 | hotel 637 | hour 638 | house 639 | housing 640 | human 641 | hunt 642 | hurry 643 | hurt 644 | husband 645 | ice 646 | idea 647 | ideal 648 | if 649 | illegal 650 | image 651 | imagination 652 | impact 653 | implement 654 | importance 655 | impress 656 | impression 657 | improvement 658 | incident 659 | income 660 | increase 661 | independence 662 | independent 663 | indication 664 | individual 665 | industry 666 | inevitable 667 | inflation 668 | influence 669 | information 670 | initial 671 | initiative 672 | injury 673 | insect 674 | inside 675 | inspection 676 | inspector 677 | instance 678 | instruction 679 | insurance 680 | intention 681 | interaction 682 | interest 683 | internal 684 | international 685 | internet 686 | interview 687 | introduction 688 | investment 689 | invite 690 | iron 691 | island 692 | issue 693 | it 694 | item 695 | jacket 696 | job 697 | join 698 | joint 699 | joke 700 | judge 701 | judgment 702 | juice 703 | jump 704 | junior 705 | jury 706 | keep 707 | key 708 | kick 709 | kid 710 | kill 711 | kind 712 | king 713 | kiss 714 | kitchen 715 | knee 716 | knife 717 | knowledge 718 | lab 719 | lack 720 | ladder 721 | lady 722 | lake 723 | land 724 | landscape 725 | language 726 | laugh 727 | law 728 | lawyer 729 | lay 730 | layer 731 | lead 732 | leader 733 | leadership 734 | leading 735 | league 736 | leather 737 | leave 738 | lecture 739 | leg 740 | length 741 | lesson 742 | let 743 | letter 744 | level 745 | library 746 | lie 747 | life 748 | lift 749 | light 750 | limit 751 | line 752 | link 753 | lip 754 | list 755 | listen 756 | literature 757 | living 758 | load 759 | loan 760 | local 761 | location 762 | lock 763 | log 764 | long 765 | look 766 | loss 767 | love 768 | low 769 | luck 770 | lunch 771 | machine 772 | magazine 773 | mail 774 | main 775 | maintenance 776 | major 777 | make 778 | male 779 | mall 780 | man 781 | management 782 | manager 783 | manner 784 | manufacturer 785 | many 786 | map 787 | march 788 | mark 789 | market 790 | marketing 791 | marriage 792 | master 793 | match 794 | mate 795 | material 796 | math 797 | matter 798 | maximum 799 | maybe 800 | meal 801 | meaning 802 | measurement 803 | meat 804 | media 805 | medicine 806 | medium 807 | meet 808 | meeting 809 | member 810 | membership 811 | memory 812 | mention 813 | menu 814 | mess 815 | message 816 | metal 817 | method 818 | middle 819 | midnight 820 | might 821 | milk 822 | mind 823 | mine 824 | minimum 825 | minor 826 | minute 827 | mirror 828 | miss 829 | mission 830 | mistake 831 | mix 832 | mixture 833 | mobile 834 | mode 835 | model 836 | mom 837 | moment 838 | money 839 | monitor 840 | month 841 | mood 842 | morning 843 | mortgage 844 | most 845 | mother 846 | motor 847 | mountain 848 | mouse 849 | mouth 850 | move 851 | movie 852 | mud 853 | muscle 854 | music 855 | nail 856 | name 857 | nasty 858 | nation 859 | national 860 | native 861 | natural 862 | nature 863 | neat 864 | necessary 865 | neck 866 | negative 867 | negotiation 868 | nerve 869 | net 870 | network 871 | news 872 | newspaper 873 | night 874 | nobody 875 | noise 876 | normal 877 | north 878 | nose 879 | note 880 | nothing 881 | notice 882 | novel 883 | number 884 | nurse 885 | object 886 | objective 887 | obligation 888 | occasion 889 | offer 890 | office 891 | officer 892 | official 893 | oil 894 | one 895 | opening 896 | operation 897 | opinion 898 | opportunity 899 | opposite 900 | option 901 | orange 902 | order 903 | ordinary 904 | organization 905 | original 906 | other 907 | outcome 908 | outside 909 | oven 910 | owner 911 | pace 912 | pack 913 | package 914 | page 915 | pain 916 | paint 917 | painting 918 | pair 919 | panic 920 | paper 921 | parent 922 | park 923 | parking 924 | part 925 | particular 926 | partner 927 | party 928 | pass 929 | passage 930 | passenger 931 | passion 932 | past 933 | path 934 | patience 935 | patient 936 | pattern 937 | pause 938 | pay 939 | payment 940 | peace 941 | peak 942 | pen 943 | penalty 944 | pension 945 | people 946 | percentage 947 | perception 948 | performance 949 | period 950 | permission 951 | permit 952 | person 953 | personal 954 | personality 955 | perspective 956 | phase 957 | philosophy 958 | phone 959 | photo 960 | phrase 961 | physical 962 | physics 963 | piano 964 | pick 965 | picture 966 | pie 967 | piece 968 | pin 969 | pipe 970 | pitch 971 | pizza 972 | place 973 | plan 974 | plane 975 | plant 976 | plastic 977 | plate 978 | platform 979 | play 980 | player 981 | pleasure 982 | plenty 983 | poem 984 | poet 985 | poetry 986 | point 987 | police 988 | policy 989 | politics 990 | pollution 991 | pool 992 | pop 993 | population 994 | position 995 | positive 996 | possession 997 | possibility 998 | possible 999 | post 1000 | pot 1001 | potato 1002 | potential 1003 | pound 1004 | power 1005 | practice 1006 | preference 1007 | preparation 1008 | presence 1009 | present 1010 | presentation 1011 | president 1012 | press 1013 | pressure 1014 | price 1015 | pride 1016 | priest 1017 | primary 1018 | principle 1019 | print 1020 | prior 1021 | priority 1022 | private 1023 | prize 1024 | problem 1025 | procedure 1026 | process 1027 | produce 1028 | product 1029 | profession 1030 | professional 1031 | professor 1032 | profile 1033 | profit 1034 | program 1035 | progress 1036 | project 1037 | promise 1038 | promotion 1039 | prompt 1040 | proof 1041 | property 1042 | proposal 1043 | protection 1044 | psychology 1045 | public 1046 | pull 1047 | punch 1048 | purchase 1049 | purple 1050 | purpose 1051 | push 1052 | put 1053 | quality 1054 | quantity 1055 | quarter 1056 | queen 1057 | question 1058 | quiet 1059 | quit 1060 | quote 1061 | race 1062 | radio 1063 | rain 1064 | raise 1065 | range 1066 | rate 1067 | ratio 1068 | raw 1069 | reach 1070 | reaction 1071 | read 1072 | reading 1073 | reality 1074 | reason 1075 | reception 1076 | recipe 1077 | recognition 1078 | recommendation 1079 | record 1080 | recording 1081 | recover 1082 | red 1083 | reference 1084 | reflection 1085 | refrigerator 1086 | refuse 1087 | region 1088 | register 1089 | regret 1090 | regular 1091 | relation 1092 | relationship 1093 | relative 1094 | release 1095 | relief 1096 | remote 1097 | remove 1098 | rent 1099 | repair 1100 | repeat 1101 | replacement 1102 | reply 1103 | report 1104 | representative 1105 | republic 1106 | reputation 1107 | request 1108 | requirement 1109 | research 1110 | reserve 1111 | resident 1112 | resist 1113 | resolution 1114 | resolve 1115 | resort 1116 | resource 1117 | respect 1118 | respond 1119 | response 1120 | responsibility 1121 | rest 1122 | restaurant 1123 | result 1124 | return 1125 | reveal 1126 | revenue 1127 | review 1128 | revolution 1129 | reward 1130 | rice 1131 | rich 1132 | ride 1133 | ring 1134 | rip 1135 | rise 1136 | risk 1137 | river 1138 | road 1139 | rock 1140 | role 1141 | roll 1142 | roof 1143 | room 1144 | rope 1145 | rough 1146 | round 1147 | routine 1148 | row 1149 | royal 1150 | rub 1151 | ruin 1152 | rule 1153 | run 1154 | rush 1155 | sad 1156 | safe 1157 | safety 1158 | sail 1159 | salad 1160 | salary 1161 | sale 1162 | salt 1163 | sample 1164 | sand 1165 | sandwich 1166 | satisfaction 1167 | save 1168 | savings 1169 | scale 1170 | scene 1171 | schedule 1172 | scheme 1173 | school 1174 | science 1175 | score 1176 | scratch 1177 | screen 1178 | screw 1179 | script 1180 | sea 1181 | search 1182 | season 1183 | seat 1184 | second 1185 | secret 1186 | secretary 1187 | section 1188 | sector 1189 | security 1190 | selection 1191 | self 1192 | sell 1193 | senior 1194 | sense 1195 | sensitive 1196 | sentence 1197 | series 1198 | serve 1199 | service 1200 | session 1201 | set 1202 | setting 1203 | sex 1204 | shake 1205 | shame 1206 | shape 1207 | share 1208 | she 1209 | shelter 1210 | shift 1211 | shine 1212 | ship 1213 | shirt 1214 | shock 1215 | shoe 1216 | shoot 1217 | shop 1218 | shopping 1219 | shot 1220 | shoulder 1221 | show 1222 | shower 1223 | sick 1224 | side 1225 | sign 1226 | signal 1227 | signature 1228 | significance 1229 | silly 1230 | silver 1231 | simple 1232 | sing 1233 | singer 1234 | single 1235 | sink 1236 | sir 1237 | sister 1238 | site 1239 | situation 1240 | size 1241 | skill 1242 | skin 1243 | skirt 1244 | sky 1245 | sleep 1246 | slice 1247 | slide 1248 | slip 1249 | smell 1250 | smile 1251 | smoke 1252 | snow 1253 | society 1254 | sock 1255 | soft 1256 | software 1257 | soil 1258 | solid 1259 | solution 1260 | somewhere 1261 | son 1262 | song 1263 | sort 1264 | sound 1265 | soup 1266 | source 1267 | south 1268 | space 1269 | spare 1270 | speaker 1271 | special 1272 | specialist 1273 | specific 1274 | speech 1275 | speed 1276 | spell 1277 | spend 1278 | spirit 1279 | spiritual 1280 | spite 1281 | split 1282 | sport 1283 | spot 1284 | spray 1285 | spread 1286 | spring 1287 | square 1288 | stable 1289 | staff 1290 | stage 1291 | stand 1292 | standard 1293 | star 1294 | start 1295 | state 1296 | statement 1297 | station 1298 | status 1299 | stay 1300 | steak 1301 | steal 1302 | step 1303 | stick 1304 | still 1305 | stock 1306 | stomach 1307 | stop 1308 | storage 1309 | store 1310 | storm 1311 | story 1312 | strain 1313 | stranger 1314 | strategy 1315 | street 1316 | strength 1317 | stress 1318 | stretch 1319 | strike 1320 | string 1321 | strip 1322 | stroke 1323 | structure 1324 | struggle 1325 | student 1326 | studio 1327 | study 1328 | stuff 1329 | stupid 1330 | style 1331 | subject 1332 | substance 1333 | success 1334 | suck 1335 | sugar 1336 | suggestion 1337 | suit 1338 | summer 1339 | sun 1340 | supermarket 1341 | support 1342 | surgery 1343 | surprise 1344 | surround 1345 | survey 1346 | suspect 1347 | sweet 1348 | swim 1349 | swimming 1350 | swing 1351 | switch 1352 | sympathy 1353 | system 1354 | table 1355 | tackle 1356 | tale 1357 | talk 1358 | tank 1359 | tap 1360 | target 1361 | task 1362 | taste 1363 | tax 1364 | tea 1365 | teach 1366 | teacher 1367 | teaching 1368 | team 1369 | tear 1370 | technology 1371 | telephone 1372 | television 1373 | tell 1374 | temperature 1375 | temporary 1376 | tennis 1377 | tension 1378 | term 1379 | test 1380 | text 1381 | thanks 1382 | theme 1383 | theory 1384 | thing 1385 | thought 1386 | throat 1387 | ticket 1388 | tie 1389 | till 1390 | time 1391 | tip 1392 | title 1393 | today 1394 | toe 1395 | tomorrow 1396 | tone 1397 | tongue 1398 | tonight 1399 | tool 1400 | tooth 1401 | top 1402 | topic 1403 | total 1404 | touch 1405 | tough 1406 | tour 1407 | tourist 1408 | towel 1409 | tower 1410 | town 1411 | track 1412 | trade 1413 | tradition 1414 | traffic 1415 | train 1416 | trainer 1417 | training 1418 | transition 1419 | transportation 1420 | trash 1421 | travel 1422 | treat 1423 | tree 1424 | trick 1425 | trip 1426 | trouble 1427 | truck 1428 | trust 1429 | truth 1430 | try 1431 | tune 1432 | turn 1433 | twist 1434 | two 1435 | type 1436 | uncle 1437 | understanding 1438 | union 1439 | unique 1440 | unit 1441 | university 1442 | upper 1443 | upstairs 1444 | use 1445 | user 1446 | usual 1447 | vacation 1448 | valuable 1449 | value 1450 | variation 1451 | variety 1452 | vast 1453 | vegetable 1454 | vehicle 1455 | version 1456 | video 1457 | view 1458 | village 1459 | virus 1460 | visit 1461 | visual 1462 | voice 1463 | volume 1464 | wait 1465 | wake 1466 | walk 1467 | wall 1468 | war 1469 | warning 1470 | wash 1471 | watch 1472 | water 1473 | wave 1474 | way 1475 | weakness 1476 | wealth 1477 | wear 1478 | weather 1479 | web 1480 | wedding 1481 | week 1482 | weekend 1483 | weight 1484 | weird 1485 | welcome 1486 | west 1487 | western 1488 | wheel 1489 | whereas 1490 | while 1491 | white 1492 | whole 1493 | wife 1494 | will 1495 | win 1496 | wind 1497 | window 1498 | wine 1499 | wing 1500 | winner 1501 | winter 1502 | wish 1503 | witness 1504 | woman 1505 | wonder 1506 | wood 1507 | word 1508 | work 1509 | worker 1510 | working 1511 | world 1512 | worry 1513 | worth 1514 | wrap 1515 | writer 1516 | writing 1517 | yard 1518 | year 1519 | yellow 1520 | yesterday 1521 | you 1522 | young 1523 | youth 1524 | zone 1525 | -------------------------------------------------------------------------------- /bot/settings.json.example: -------------------------------------------------------------------------------- 1 | // This is a comment 2 | // Inline comments are not allowed 3 | // Required keys are given with an example 4 | // Optional keys are given with their default or an example 5 | // Note that comments are lost when the config file is written by the bot 6 | { 7 | "server": "irc.libera.chat", 8 | 9 | // Defaults to 6697 if SSL is enabled 10 | //"port": 6667, 11 | 12 | //"ssl": false, 13 | 14 | // With SASL enabled, also used to authenticate 15 | "nick": "CeBot", 16 | 17 | //"user": "cebot", 18 | 19 | // With SASL enabled, used to authenticate, 20 | // else, if present, used as server password 21 | //"password": "secret", 22 | 23 | // Send REGAIN to NickServ on nick collisions 24 | //"nickserv_regain": true, 25 | 26 | //"try_sasl": false, 27 | 28 | //"realname": "CeBot", 29 | 30 | "channels": [ 31 | "##cebot" 32 | ], 33 | 34 | // Valid levels: debug, info, warn, error, fatal 35 | //"log_level": "info", 36 | 37 | // A list of nicks to ignore all messages from 38 | //"ignores": ["troll"], 39 | 40 | "plugins": { 41 | "Admin": { 42 | //"admins": ["asterite"], 43 | "superadmins": ["jhass"] 44 | }, 45 | 46 | "Memo": { 47 | "store": "data/memo.json" 48 | }, 49 | 50 | "KeyValueStore": { 51 | "store": "data/key_value_store.json" 52 | }, 53 | 54 | // "CrystalEval": { 55 | // channels is valid for any plugin and limits the list of channels 56 | // the plugin see messages for. If not given or null, 57 | // no filter is applied, if an empty array, or false, the plugin is only 58 | // active in direct queries to the bot. 59 | // "channels": ["#crystal-lang"] 60 | // }, 61 | 62 | // "GithubIssues": { 63 | // "repositories": { 64 | // "#cebot": "jhass/CeBot" 65 | // } 66 | // }, 67 | 68 | // "Wiki": { 69 | // "wikis": { 70 | // "#diaspora": "https://wiki.diasporafoundation.org/" 71 | // } 72 | // }, 73 | 74 | "WtiStatus": { 75 | "default_project": "diaspora", 76 | "projects": { 77 | "diaspora": { 78 | "api_key": "abc", 79 | "slug": "3020-Diaspora" 80 | } 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /bot/src/main.cr: -------------------------------------------------------------------------------- 1 | require "framework/bot" 2 | require "./plugins/admin" 3 | require "./plugins/crystal_eval" 4 | require "./plugins/diaspora_version" 5 | require "./plugins/github_issues" 6 | require "./plugins/hangman" 7 | require "./plugins/hello_world" 8 | require "./plugins/key_value_store" 9 | require "./plugins/memo" 10 | require "./plugins/password" 11 | require "./plugins/programming_excuses" 12 | require "./plugins/russian_roulette" 13 | require "./plugins/what_the_commit" 14 | require "./plugins/wiki" 15 | require "./plugins/wti_status" 16 | 17 | bot = Framework::Bot.create do 18 | config.from_file "settings.json" 19 | 20 | add_plugin Admin 21 | add_plugin CrystalEval 22 | add_plugin DiasporaVersion 23 | add_plugin GithubIssues 24 | add_plugin Hangman 25 | add_plugin HelloWorld 26 | add_plugin KeyValueStore 27 | add_plugin Memo 28 | add_plugin Password 29 | add_plugin ProgrammingExcuses 30 | add_plugin RussianRoulette 31 | add_plugin WhatTheCommit 32 | add_plugin Wiki 33 | add_plugin WtiStatus 34 | end 35 | 36 | bot.start 37 | -------------------------------------------------------------------------------- /bot/src/plugins/admin.cr: -------------------------------------------------------------------------------- 1 | require "framework/plugin" 2 | 3 | class Admin 4 | include Framework::Plugin 5 | 6 | config({ 7 | admins: {type: Array(String)?}, 8 | superadmins: {type: Array(String), default: [] of String}, 9 | }) 10 | 11 | def admins 12 | config.admins ||= [] of String 13 | end 14 | 15 | def superadmin?(user) 16 | return false unless user.authname 17 | config.superadmins.includes? user.authname 18 | end 19 | 20 | def admin?(user) 21 | return true if superadmin? user 22 | return false unless user.authname 23 | admins.includes? user.authname 24 | end 25 | 26 | # channel = ( "#" / "+" / ( "!" channelid ) / "&" ) chanstring 27 | # [ ":" chanstring ] 28 | # chanstring = ; any octet except NUL, BELL, CR, LF, " ", "," and ":" 29 | # channelid = ; 5( A-Z / 0-9 ) 30 | 31 | match /^!(join|part)\s+(#[^\s,:]+)?/ 32 | match /^!(msg|sayto|doto)\s+([^ ]+)\s+(.+)/ 33 | match /^!(addadmin|rmadmin)\s+([^ ]+)/ 34 | match /^!(ignore|unignore)\s+([^ ]+)/ 35 | match /^!(enable|disable)\s+(#[^\s,:]+)\s+([a-zA-Z]+)/ 36 | match /^!(enable|disable)\s+([a-zA-Z]+)$/ 37 | match /^!((?:de)?op)(?:\s+(\w+))?$/ 38 | match /^!(quit|reload)$/ 39 | 40 | def execute(msg, match) 41 | return unless admin?(msg.sender) 42 | 43 | case match[1] 44 | when "join", "part", "sayto", "msg", "doto" 45 | with_channel msg, match 46 | when "addadmin" 47 | add_admin msg, match[2] 48 | when "rmadmin" 49 | remove_admin msg, match[2] 50 | when "ignore" 51 | add_ignore msg, match[2] 52 | when "unignore" 53 | remove_ignore msg, match[2] 54 | when "enable", "disable" 55 | channel = match[3]? ? channel(match[2]) : msg.channel 56 | plugin = match[3]? ? match[3] : match[2] 57 | if match[1] == "enable" 58 | enable_plugin msg, channel, plugin 59 | else 60 | disable_plugin msg, channel, plugin 61 | end 62 | when "op" 63 | op :op, msg, match[2]? 64 | when "deop" 65 | op :deop, msg, match[2]? 66 | when "reload" 67 | context.config.reload 68 | msg.reply "#{msg.sender.nick}: Reloaded configuration." 69 | when "quit" 70 | context.connection.quit 71 | else 72 | # regexes allow no other values 73 | end 74 | end 75 | 76 | def with_channel(msg, match) 77 | channel = match[2] 78 | if channel.empty? 79 | return unless msg.channel? 80 | channel = msg.channel.name 81 | end 82 | 83 | case match[1] 84 | when "join" 85 | return unless channel 86 | msg.context.join channel 87 | when "part" 88 | return unless channel 89 | msg.context.part channel 90 | when "msg", "sayto" 91 | sayto msg, match[2], match[3] 92 | when "doto" 93 | doto msg, match[2], match[3] 94 | else 95 | # regexes don't allow other 96 | end 97 | end 98 | 99 | def sayto(msg, dst, message) 100 | sendto(msg, dst) do |target| 101 | target.send message 102 | end 103 | end 104 | 105 | def doto(msg, dst, message) 106 | sendto(msg, dst) do |target| 107 | target.action message 108 | end 109 | end 110 | 111 | def sendto(msg, dst) 112 | if dst.starts_with? '#' 113 | if context.channels.includes?(dst) 114 | yield channel(dst) 115 | else 116 | msg.reply "I'm not in #{dst}." 117 | end 118 | else 119 | yield user(dst) 120 | end 121 | end 122 | 123 | def op(mode, msg, target) 124 | return unless msg.channel? 125 | 126 | target = target ? user(target) : bot 127 | if !msg.channel.has? target 128 | msg.reply "#{msg.sender.nick}: #{target.nick} isn't in this channel." 129 | elsif (msg.channel.opped?(target) ? :op : :deop) == mode 130 | msg.reply "No change necessary." 131 | elsif msg.channel.opped? bot 132 | case mode 133 | when :op 134 | msg.channel.op target 135 | when :deop 136 | msg.channel.deop target 137 | else 138 | # either or above 139 | end 140 | else 141 | user("ChanServ").send "#{mode.to_s.upcase} #{msg.channel.name} #{target.nick}" 142 | end 143 | end 144 | 145 | def add_admin(msg, nick) 146 | return unless superadmin? msg.sender 147 | 148 | new_admin = user(nick) 149 | 150 | if admin? new_admin 151 | msg.reply "#{msg.sender.nick}: #{nick} is already an admin." 152 | elsif bot.nick == nick 153 | msg.reply "#{msg.sender.nick}: That makes no sense." 154 | else 155 | authname = new_admin.authname 156 | if authname.is_a? String 157 | admins << authname 158 | config.save(context.config) 159 | msg.reply "#{msg.sender.nick}: Added #{nick} to admins." 160 | else 161 | msg.reply "#{msg.sender.nick}: #{nick} is not authenticated." 162 | end 163 | end 164 | end 165 | 166 | def remove_admin(msg, nick) 167 | return unless superadmin? msg.sender 168 | 169 | authname = user(nick).authname 170 | authname = {authname, nick}.find { |name| admins.includes? name } 171 | 172 | if authname 173 | admins.delete authname 174 | config.save(context.config) 175 | msg.reply "#{msg.sender.nick}: Removed #{nick} from admins." 176 | else 177 | msg.reply "#{msg.sender.nick}: #{nick} is not an admin." 178 | end 179 | end 180 | 181 | def add_ignore(msg, nick) 182 | return unless admin? msg.sender 183 | 184 | unless context.config.ignores.includes? nick 185 | context.config.ignores << nick 186 | context.config.save 187 | msg.reply "#{msg.sender.nick}: I will ignore #{nick} now." 188 | else 189 | msg.reply "#{msg.sender.nick}: I ignore #{nick} already." 190 | end 191 | end 192 | 193 | def remove_ignore(msg, nick) 194 | return unless admin? msg.sender 195 | 196 | if context.config.ignores.includes? nick 197 | context.config.ignores.delete(nick) 198 | context.config.save 199 | msg.reply "#{msg.sender.nick}: I will no longer ignore #{nick}." 200 | else 201 | msg.reply "#{msg.sender.nick}: I do not ignore #{nick}." 202 | end 203 | end 204 | 205 | def enable_plugin(msg, channel, name) 206 | unless context.config.plugins.has_key? name 207 | msg.reply "#{msg.sender.nick}: Unknown plugin #{name}." 208 | return 209 | end 210 | 211 | plugin_config = context.config.plugins[name].config 212 | 213 | if plugin_config.listens_to? channel 214 | msg.reply "#{msg.sender.nick}: #{name} is already enabled." 215 | return 216 | end 217 | 218 | plugin_config.channels!.add channel 219 | plugin_config.save(context.config) 220 | 221 | msg.reply "#{msg.sender.nick}: Enabled #{name}." 222 | end 223 | 224 | def disable_plugin(msg, channel, name) 225 | unless context.config.plugins.has_key? name 226 | msg.reply "#{msg.sender.nick}: Unknown plugin #{name}." 227 | return 228 | end 229 | 230 | plugin_config = context.config.plugins[name].config 231 | 232 | unless plugin_config.listens_to? channel 233 | msg.reply "#{msg.sender.nick}: #{name} is already disabled." 234 | return 235 | end 236 | 237 | plugin_config.channels!.remove channel 238 | plugin_config.save(context.config) 239 | 240 | msg.reply "#{msg.sender.nick}: Disabled #{name}." 241 | end 242 | end 243 | -------------------------------------------------------------------------------- /bot/src/plugins/crystal_eval.cr: -------------------------------------------------------------------------------- 1 | require "http/client" 2 | 3 | require "framework/plugin" 4 | 5 | class CrystalEval 6 | include Framework::Plugin 7 | 8 | struct Request 9 | include JSON::Serializable 10 | 11 | getter run : Run 12 | end 13 | 14 | struct Run 15 | include JSON::Serializable 16 | 17 | getter stdout : String 18 | getter stderr : String 19 | getter exit_code : Int32 20 | getter html_url : String 21 | end 22 | 23 | TEMPLATE_PLACEHOLDER = "%body" 24 | TEMPLATE = <<-END 25 | macro __wrap_last_expression(exprs) 26 | {% for expression in exprs.expressions[0..-2] %} 27 | {{expression}} 28 | {% end %} 29 | {% if %w[Def FunDef Macro ClassDef LibDef].includes? exprs.expressions.last.class_name %} 30 | {{exprs.expressions.last}} 31 | puts "#=> nil" 32 | {% else %} 33 | %expr = begin 34 | {{exprs.expressions.last}} 35 | end 36 | puts "\\n# => \#{ %expr.inspect}" 37 | {% end %} 38 | end 39 | 40 | __wrap_last_expression begin; nil 41 | #{TEMPLATE_PLACEHOLDER} 42 | end 43 | END 44 | 45 | match /^>>\s*(.+)/ 46 | 47 | def execute(msg, match) 48 | source = TEMPLATE.sub(TEMPLATE_PLACEHOLDER, match[1]) 49 | 50 | run = Request.from_json((JSON.parse( 51 | HTTP::Client.post( 52 | "https://carc.in/run_requests", 53 | HTTP::Headers {"Content-Type" => "application/json; charset=utf8"}, 54 | { 55 | run_request: { 56 | language: "crystal", 57 | code: source 58 | } 59 | }.to_json 60 | ).body 61 | ))["run_request"].to_json).run 62 | 63 | output = run.stdout 64 | stderr = run.stderr 65 | success = run.exit_code == 0 66 | 67 | if stderr && !stderr.strip.empty? 68 | playpen, crystal = separate_playpen stderr 69 | reply = crystal.last? 70 | 71 | # Exception? 72 | reply = crystal.first if reply && reply.match(/^\[\d+\]/) 73 | 74 | reply = "Sorry, that took too long." if playpen.includes?("playpen: timeout triggered!") 75 | end 76 | 77 | if reply.nil? && output && !output.strip.empty? 78 | reply = success ? output.lines.find {|line| !line.strip.empty? } : 79 | filter_wrapper_macro(find_error_message(output)) 80 | elsif success 81 | reply ||= output # Return the empty string 82 | end 83 | 84 | reply ||= "Failed to run your code, sorry!" 85 | 86 | reply = strip_ansi_codes reply 87 | reply = prettify_error reply 88 | reply = limit_size reply 89 | 90 | msg.reply "#{msg.sender.nick}: #{reply.chomp} - #{"more at " if success && output.lines.size > 2}#{run.html_url}" 91 | end 92 | 93 | def separate_playpen(stderr) 94 | stderr.lines.reject(&.strip.empty?).partition &.starts_with?("playpen:") 95 | end 96 | 97 | def find_error_message(output) 98 | lines = output.lines.reject(&.strip.empty?) 99 | 100 | # Compiler bug 101 | bug = lines.find {|line| line.starts_with?("Bug:") } 102 | return bug if bug 103 | 104 | # Find syntax error in macro expansion 105 | if separator = lines.find {|line| line =~ /^-+$/ } 106 | if index = lines.rindex(separator) 107 | return lines[index+1] 108 | end 109 | end 110 | 111 | # Overload listing? Error is before that 112 | if index = lines.index {|line| line.starts_with? "Overloads are:" } 113 | return lines[index-1] 114 | end 115 | 116 | # Rip out any type traces 117 | if separator = lines.find {|line| line =~ /^=+$/ } 118 | if index = lines.index(separator) 119 | lines = lines[0...index] 120 | end 121 | end 122 | 123 | # Syntax error 124 | syntax = lines.find {|line| line.includes?("Syntax error") } 125 | return syntax if syntax 126 | 127 | # Check if we got a traceback 128 | traces = lines.select {|line| 129 | line =~ /\/[\.\w]+:\d+:\s/ || 130 | line =~ /in line \d+:/ || 131 | line =~ /in macro/ 132 | } 133 | 134 | unless traces.empty? 135 | line = traces.last 136 | if line.includes?("in macro") 137 | return "#{line} #{lines.last}" 138 | else 139 | return line 140 | end 141 | end 142 | 143 | # No traceback, first line that starts with "Error" then 144 | lines.find &.starts_with?("Error") 145 | end 146 | 147 | def filter_wrapper_macro(error) 148 | error && error.lines.reject {|e| 149 | e.includes?("in macro '__wrap_last_expression'") 150 | }.join(" ") 151 | end 152 | 153 | def strip_ansi_codes(text) 154 | text.gsub(/\e\[(?:\d\d;)?[01]m/, "") 155 | end 156 | 157 | def prettify_error(text) 158 | if text.includes?("Bad system call") || text.includes?(": 31") 159 | "Sorry, I can't let you do that." 160 | else 161 | text 162 | end 163 | end 164 | 165 | def limit_size(text, limit=350) 166 | text.size > limit ? "#{text[0, limit]} ..." : text 167 | end 168 | end 169 | -------------------------------------------------------------------------------- /bot/src/plugins/diaspora_version.cr: -------------------------------------------------------------------------------- 1 | require "http/client" 2 | 3 | require "framework/plugin" 4 | 5 | class DiasporaVersion 6 | include Framework::Plugin 7 | 8 | API_ENDPOINT = "https://version.diaspora.social/%s/text" 9 | 10 | match /^!rev\s+([a-zA-Z0-9]+[a-zA-Z0-9\-\.]*\.[a-zA-Z]+)/ 11 | 12 | def execute(msg, match) 13 | resp = HTTP::Client.get API_ENDPOINT % [match[1]] 14 | msg.reply resp.body 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /bot/src/plugins/github_issues.cr: -------------------------------------------------------------------------------- 1 | require "framework/plugin" 2 | require "http/client" 3 | require "json" 4 | 5 | class GithubIssues 6 | include Framework::Plugin 7 | 8 | struct Issue 9 | include JSON::Serializable 10 | 11 | getter html_url : String 12 | getter title : String 13 | 14 | def to_s(io) 15 | io << html_url << " (#{title})" 16 | end 17 | end 18 | 19 | 20 | PATTERN = /(?:^|\s+|\()#(\d{3,5})\b/ 21 | 22 | config({ 23 | repositories: {type: Hash(String, String), default: {} of String => String} 24 | }) 25 | 26 | match PATTERN 27 | def execute(msg, _match) 28 | return unless msg.channel? 29 | return unless config.repositories.has_key? msg.channel.name 30 | issues = msg.message.scan(PATTERN) 31 | msg.reply issues.map {|issue| fetch_issue config.repositories[msg.channel.name], issue[1]}.compact.join(" | ") 32 | end 33 | 34 | private def fetch_issue(repo, issue_id) 35 | api_response = HTTP::Client.get("https://api.github.com/repos/#{repo}/issues/#{issue_id}", 36 | headers: HTTP::Headers{"Accept" => ["application/vnd.github.v3+json"], "User-Agent" => ["CeBot"]}) 37 | if api_response.status_code == 200 38 | Issue.from_json api_response.body 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /bot/src/plugins/hangman.cr: -------------------------------------------------------------------------------- 1 | require "framework/plugin" 2 | require "framework/limiter" 3 | 4 | class Hangman 5 | include Framework::Plugin 6 | 7 | class Game 8 | def self.word_list_for(name) 9 | File.read_lines(File.join(__DIR__, "..", "..", "res", name)).map(&.chomp) 10 | end 11 | 12 | WORDLISTS = { 13 | "brainfuck" => word_list_for("brainfuck"), 14 | "c" => word_list_for("glibc_functions"), 15 | "codepoints" => word_list_for("unicode_codepoint_names"), 16 | "coreutils" => word_list_for("coreutils"), 17 | "cpan" => word_list_for("cpan_packages"), 18 | "crystal" => word_list_for("crystal"), 19 | "distros" => word_list_for("distros"), 20 | "french" => word_list_for("wordlist_fr"), 21 | "gems" => word_list_for("gem_names"), 22 | "german" => word_list_for("wordlist_de"), 23 | "git" => word_list_for("git"), 24 | "haskell" => word_list_for("haskell"), 25 | "irssi" => word_list_for("irssi"), 26 | "java" => word_list_for("java_classes"), 27 | "javascript" => word_list_for("javascript"), 28 | "nouns" => word_list_for("wordlist"), 29 | "perl" => word_list_for("perl"), 30 | "php" => word_list_for("php"), 31 | "pokemon" => word_list_for("pokemon"), 32 | "ruby" => word_list_for("ruby"), 33 | } 34 | 35 | DEFAULT_LIST = "nouns" 36 | DEFAULT_GUESS_MAX = 12 37 | PLACEHOLDER = '␣' 38 | 39 | @word : Array(Char) 40 | 41 | def initialize(@list = DEFAULT_LIST, @guess_max = DEFAULT_GUESS_MAX) 42 | @word = pick_word list 43 | @guess_max = guess_max 44 | @guesses = [] of Char | String 45 | end 46 | 47 | def status 48 | String.build do |io| 49 | io << current_word 50 | io << " [#{wrong_guesses.join}]" 51 | io << " #{wrong_guesses.size}/#{@guess_max}" 52 | io << " (#{@list})" 53 | io << " You won!" if won? 54 | io << " You lost!" if lost? 55 | end 56 | end 57 | 58 | def current_word 59 | return @word.join if lost? 60 | known_chars 61 | end 62 | 63 | def known_chars 64 | @word.map { |char| 65 | @guesses.includes?(char.downcase) ? char : PLACEHOLDER 66 | }.join 67 | end 68 | 69 | def wrong_guesses 70 | @guesses - @word.map(&.downcase) 71 | end 72 | 73 | def guess(guesses : Array(Char)) 74 | guesses.each do |guess| 75 | guess guess 76 | end 77 | end 78 | 79 | def guess(guess : Char) 80 | return if over? 81 | @guesses << guess.downcase unless guessed? guess 82 | end 83 | 84 | def guessed?(guess) 85 | @guesses.includes? guess.downcase 86 | end 87 | 88 | def lost? 89 | wrong_guesses.size >= @guess_max 90 | end 91 | 92 | def won? 93 | !lost? && !known_chars.includes?(PLACEHOLDER) 94 | end 95 | 96 | def over? 97 | lost? || won? 98 | end 99 | 100 | private def pick_word(list) 101 | WORDLISTS[list].sample.chars 102 | end 103 | end 104 | 105 | GAMES = {} of String => Game 106 | LIMITS = Framework::LimiterCollection(Framework::User).new 3, 60 107 | 108 | listen :message 109 | 110 | def react_to(event) 111 | msg = event.message 112 | return unless msg.channel? 113 | message = msg.message 114 | return unless message.starts_with? bot.nick 115 | 116 | command = message.gsub(/^#{bot.nick}[:,]?\s*/, "") 117 | case command 118 | when /^!hangman\s+(?:lists?|help)$/ 119 | list msg 120 | when /^!hangman\s+\w+$/ 121 | _c, list = command.split 122 | start_game msg, list, Game::DEFAULT_GUESS_MAX 123 | when /^!hangman\s+\w+\s+\d+$/ 124 | _c, list, guess_max = command.split 125 | start_game msg, list, guess_max.to_i 126 | when /^!hangman$/ 127 | start_game msg, Game::DEFAULT_LIST, Game::DEFAULT_GUESS_MAX 128 | else 129 | react_to_guess msg, command 130 | end 131 | end 132 | 133 | def react_to_guess(msg, command) 134 | return unless LIMITS.pass? msg.sender 135 | LIMITS.hit msg.sender 136 | 137 | case command 138 | when /^space$/ 139 | guess msg, ' ' 140 | when /^[a-zA-Z0-9!"#\$%&'\*\+,\-\.\/:;<=>\?@\[\]\\^_`|~ ]+$/ 141 | guess msg, command.downcase.chars 142 | else 143 | # ignore 144 | end 145 | end 146 | 147 | def start_game(msg, list, guess_max) 148 | unless GAMES.has_key? msg.channel.name 149 | if Game::WORDLISTS.has_key? list 150 | GAMES[msg.channel.name] = Game.new list, guess_max.clamp(1, Game::DEFAULT_GUESS_MAX) 151 | else 152 | return 153 | end 154 | end 155 | 156 | msg.reply GAMES[msg.channel.name].status 157 | end 158 | 159 | def list(msg) 160 | msg.reply "#{msg.sender.nick}: The following word lists are available: #{Game::WORDLISTS.keys.join(", ")}" 161 | end 162 | 163 | def guess(msg, guess) 164 | return unless GAMES.has_key? msg.channel.name 165 | 166 | game = GAMES[msg.channel.name] 167 | game.guess guess 168 | msg.reply game.status 169 | GAMES.delete(msg.channel.name) if game.over? 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /bot/src/plugins/hello_world.cr: -------------------------------------------------------------------------------- 1 | require "framework/plugin" 2 | 3 | class HelloWorld 4 | include Framework::Plugin 5 | 6 | match /!hello_world/ 7 | def execute(msg, match) 8 | msg.reply "Hi #{msg.sender.nick}!" 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /bot/src/plugins/key_value_store.cr: -------------------------------------------------------------------------------- 1 | require "framework/plugin" 2 | require "framework/json_store" 3 | 4 | # nickname = ( letter / special ) *8( letter / digit / special / "-" ) 5 | # letter = [a-zA-Z] 6 | # special = [\[\]\\`_^{}|] 7 | # digit = \d 8 | # [\w\[\]\\`^{}|][\w\[\]\\`^{}|\d\-]{0,8} 9 | 10 | class KeyValueStore 11 | include Framework::Plugin 12 | 13 | config({ 14 | store: {type: String, default: "data/key_value_store.json"} 15 | }) 16 | 17 | def self.config_loaded(config) 18 | @@store = Framework::JsonStore(String, Hash(String, String)).new config.store 19 | end 20 | 21 | def store 22 | @@store.not_nil! 23 | end 24 | 25 | match /^!(keys)\s*(#[^ ]+)?/ 26 | match /^\?([\d\w\-]++)(?!=)\s*([\w\[\]\\`\^\{\}\|][\w\[\]\\`\^\{\}|\d\-]{0,15})?/ 27 | match /^\?([\d\w\-]+=)(\?[\d\w\-]+)\s*$/ 28 | match /^\?([\d\w\-]+=)(?!\?)(.+)/ 29 | 30 | def execute(msg, match) 31 | if match[1] == "keys" 32 | explicit_channel = match[2] if match[2]? 33 | channel = msg.channel.name if msg.channel? 34 | channel = explicit_channel || channel 35 | return unless channel 36 | 37 | keys = known_keys(channel) 38 | channel = explicit_channel ? " for #{channel}" : "" 39 | 40 | unless keys.empty? 41 | msg.reply "I know the following keys#{channel}: #{keys.join(", ")}" 42 | else 43 | msg.reply "No keys known#{channel}." 44 | end 45 | else 46 | return unless msg.channel? 47 | 48 | if match[1].ends_with? '=' 49 | key = match[1][0..-2] 50 | set_key msg.channel.name, key, match[2] 51 | msg.reply "#{msg.sender.nick}: Set #{key}." 52 | else 53 | content = get_key msg.channel.name, match[1] 54 | if content 55 | prefix = "#{match[2]}: " if match[2]? 56 | msg.reply "#{prefix}#{content}" 57 | else 58 | msg.reply "#{msg.sender.nick}: Nothing known about #{match[1]}." 59 | end 60 | end 61 | end 62 | end 63 | 64 | def known_keys(channel) 65 | store.fetch(channel) do |data| 66 | data ? data.keys : Tuple.new 67 | end 68 | end 69 | 70 | def set_key(channel, key, value) 71 | store.modify(channel) do |data| 72 | data ||= Hash(String, String).new 73 | 74 | 10.times do 75 | current_value = data[key]? 76 | if current_value && current_value.starts_with?('?') && !value.starts_with?('?') 77 | key = current_value[1..-1] 78 | else 79 | break 80 | end 81 | end 82 | 83 | data[key] = value 84 | data 85 | end 86 | end 87 | 88 | def get_key(channel, key) 89 | store.fetch(channel) do |data| 90 | 10.times do 91 | value = data && data[key]? 92 | if value && value.starts_with?('?') 93 | key = value[1..-1] 94 | else 95 | return value 96 | end 97 | end 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /bot/src/plugins/memo.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | require "framework/plugin" 4 | require "framework/json_store" 5 | 6 | class Memo 7 | struct Memo 8 | include JSON::Serializable 9 | 10 | getter content : String 11 | getter sender : String 12 | @[JSON::Field(emit_null: true)] 13 | getter context : String? 14 | @[JSON::Field(converter: Time::Format.new("%F %T"))] 15 | getter timestamp : Time 16 | 17 | def initialize(@content, @sender, @context, @timestamp) 18 | end 19 | 20 | def to_s(io) 21 | io << @timestamp.to_s("%D %R") << ' ' << @sender 22 | io << " (" << context << ")" if context = @context 23 | io << ": " << @content 24 | end 25 | end 26 | 27 | include Framework::Plugin 28 | 29 | config({ 30 | store: {type: String, default: "data/memos.json"}, 31 | }) 32 | 33 | def self.config_loaded(config) 34 | @@memos = Framework::JsonStore(String, Array(Memo)).new config.store 35 | end 36 | 37 | def memos 38 | @@memos.not_nil! 39 | end 40 | 41 | listen :join 42 | listen :nick 43 | listen :message 44 | 45 | def react_to(event) 46 | deliver_memos event.sender 47 | end 48 | 49 | match /^!memo\s+([^ ]+?)\s+(.+)/ 50 | 51 | def execute(msg, match) 52 | nick = match[1] 53 | 54 | if nick == msg.sender.nick 55 | msg.reply "#{msg.sender.nick}: You can't leave memos for yourself ..." 56 | elsif nick == bot.nick 57 | msg.reply "#{msg.sender.nick}: You can't leave memos for me ..." 58 | else 59 | store_memo match[2], msg.sender.nick, nick, msg.channel?.try(&.name) 60 | msg.reply "#{msg.sender.nick}: Added memo for #{nick}." 61 | end 62 | end 63 | 64 | def deliver_memos(user) 65 | chosen_keys = Set(String).new 66 | 67 | memos.keys.select { |key| 68 | key == user.nick || (key.starts_with?('/') && key.ends_with?('/')) 69 | }.each do |key| 70 | regex = key.starts_with?('/') ? key[1..-2] : Regex.escape(key) 71 | regex = Regex.new regex rescue nil 72 | 73 | if regex && user.nick.match(regex) 74 | memos.fetch(key) do |memos| 75 | next unless memos 76 | memos.each do |memo| 77 | chosen_keys << key 78 | user.send memo.to_s 79 | end 80 | end 81 | end 82 | end 83 | 84 | chosen_keys.each do |key| 85 | memos.modify(key) do |memos| 86 | memos ||= [] of Memo 87 | memos.clear 88 | memos 89 | end 90 | end 91 | end 92 | 93 | def store_memo(content, from, to, at = nil) 94 | memos.modify(to) do |memos| 95 | memos ||= [] of Memo 96 | memos << Memo.new(content, from, at, Time.local) 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /bot/src/plugins/password.cr: -------------------------------------------------------------------------------- 1 | require "framework/plugin" 2 | 3 | class Password 4 | include Framework::Plugin 5 | AVAILABLE_DICTS = { 6 | "default" => "/usr/share/dict/words", 7 | "en" => "/usr/share/dict/american-english", 8 | "de" => "/usr/share/dict/ngerman" 9 | } 10 | 11 | match /^!password\s*$/ 12 | match /^!password\s+(\w\w)/ 13 | def execute(msg, match) 14 | lang = match[1]? || "default" 15 | dict = AVAILABLE_DICTS[lang.strip] 16 | dict ||= AVAILABLE_DICTS["default"] 17 | password = `echo "$(shuf -n4 #{dict} | tr '\n' ' ')"`.delete('\'') 18 | msg.reply password.downcase 19 | rescue 20 | msg.reply "I'm out of passwords currently :(" 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /bot/src/plugins/programming_excuses.cr: -------------------------------------------------------------------------------- 1 | require "http/client" 2 | 3 | require "framework/plugin" 4 | 5 | class ProgrammingExcuses 6 | include Framework::Plugin 7 | 8 | match /^!excuse/ 9 | def execute(m, _match) 10 | html = HTTP::Client.get("http://programmingexcuses.com/").body 11 | excuse = html[/]+>]+>([^<]+) String} 8 | }) 9 | 10 | match /^!wiki\s+(.+)/ 11 | 12 | def execute(msg, match) 13 | return unless msg.channel? 14 | return unless config.wikis.has_key? msg.channel.name 15 | title = match[1].squeeze(" ").strip.tr(" ", "_").capitalize 16 | msg.reply "#{config.wikis[msg.channel.name]}#{title}" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /bot/src/plugins/wti_status.cr: -------------------------------------------------------------------------------- 1 | require "http/client" 2 | require "json" 3 | 4 | require "framework/plugin" 5 | 6 | class WtiStatus 7 | struct Stats 8 | include JSON::Serializable 9 | 10 | getter count_strings_done : Int32 11 | getter count_strings : Int32 12 | getter count_strings_to_translate : Int32 13 | getter count_strings_to_proofread : Int32 14 | getter count_strings_to_verify : Int32 15 | end 16 | 17 | struct Project 18 | include JSON::Serializable 19 | 20 | getter api_key : String 21 | getter slug : String 22 | end 23 | 24 | include Framework::Plugin 25 | 26 | config({ 27 | projects: {type: Hash(String, Project), default: {} of String => Project}, 28 | default_project: {type: String, default: "configure_me"} 29 | }) 30 | 31 | # command (?:ts|trans(?:lation)?stati?s(?:tics)?) 32 | # code ([a-zA-Z0-9_-]+) 33 | match /^!(?:ts|trans(?:lation)?stati?s(?:tics)?)\s+([a-zA-Z0-9_-]+)$/ 34 | match /^!(?:ts|trans(?:lation)?stati?s(?:tics)?)\s+(\w+)\s+([a-zA-Z0-9_-]+)$/ 35 | 36 | def execute(msg, match) 37 | project = match.size == 3 ? match[1] : config.default_project 38 | code = match.size == 3 ? match[2] : match[1] 39 | code = code.tr("_", "-") 40 | 41 | if code == "en" 42 | msg.reply "English is the master translation ;)" 43 | return 44 | end 45 | 46 | unless config.projects.has_key? project 47 | msg.reply "#{msg.sender.nick}: Unconfigured project #{project}." 48 | return 49 | end 50 | 51 | url = "https://webtranslateit.com/api/projects/#{config.projects[project].api_key}/stats.json" 52 | content = HTTP::Client.get(url).body 53 | stats = Hash(String, Stats).from_json content 54 | 55 | if stats.has_key?(code) 56 | stats = stats[code] 57 | if stats.count_strings_done == stats.count_strings 58 | msg.reply "The translation for #{code} is complete :)." 59 | else 60 | msg.reply "The translation for #{code} has #{stats.count_strings_done}/#{stats.count_strings} keys done, with #{stats.count_strings_to_translate} untranslated, #{stats.count_strings_to_proofread} to proofread and #{stats.count_strings_to_verify} to verify." 61 | end 62 | msg.reply " Join the team at https://webtranslateit.com/en/projects/#{config.projects[project].slug} to further improve it!" 63 | else 64 | msg.reply "There is no translation for #{code} yet. Have a look at https://wiki.diasporafoundation.org/Contribute_translations on how to create it!" 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /bot/src/simple.cr: -------------------------------------------------------------------------------- 1 | require "framework/bot" 2 | require "framework/plugin" 3 | 4 | class HelloWorld 5 | include Framework::Plugin 6 | 7 | match /!hello_world/ 8 | 9 | def execute(msg, match) 10 | msg.reply "Hi #{msg.sender.nick}!" 11 | end 12 | end 13 | 14 | bot = Framework::Bot.create do 15 | config.server = "irc.libera.chat" 16 | config.nick = "CrystalBot" 17 | config.channels = ["##cebot"] 18 | config.log_level = :debug 19 | 20 | add_plugin HelloWorld 21 | end 22 | 23 | bot.start 24 | -------------------------------------------------------------------------------- /framework/src/framework/bot.cr: -------------------------------------------------------------------------------- 1 | require "signal" 2 | 3 | require "irc/connection" 4 | require "irc/message" 5 | 6 | require "./configuration" 7 | require "./event" 8 | require "./filter" 9 | require "./message" 10 | require "./channel" 11 | require "./plugin_container" 12 | require "./plugin" 13 | 14 | module Framework 15 | Log = IRC::Log.for("Framework") 16 | 17 | class Bot 18 | getter config : Configuration 19 | getter! connection : IRC::Connection 20 | getter! user : User 21 | delegate channels, to: config 22 | @filters : Array(Filter::Item) 23 | 24 | def self.create 25 | new.tap do |bot| 26 | with bot yield 27 | end 28 | end 29 | 30 | private def initialize 31 | @config = Configuration.new 32 | @filters = [] of Filter::Item 33 | @started = false 34 | 35 | add_filter Filter::NickFilter.new(config) 36 | end 37 | 38 | macro add_plugin(klass) 39 | config.add_plugin Framework::PluginContainer({{klass}}, {{klass}}::Config).new 40 | end 41 | 42 | def add_filter(filter : Filter::Item) 43 | @filters << filter 44 | end 45 | 46 | def join(name) 47 | return if config.channels.includes?(name) && @started 48 | 49 | channel = connection.join name 50 | 51 | if @started 52 | config.channels << name 53 | config.save 54 | end 55 | 56 | channel.on_message do |message| 57 | message = Message.new self, message 58 | event = Event.new(self, :message, message) 59 | config.plugins.each_value &.handle(event) 60 | end 61 | 62 | Channel.from_name(name, self) 63 | end 64 | 65 | def part(name) 66 | connection.part name if config.channels.includes? name 67 | config.channels.delete name 68 | config.save 69 | end 70 | 71 | def start 72 | connection = config.to_connection 73 | @connection = connection 74 | @user = User.from_nick config.nick, self 75 | user.mask.user = config.user 76 | 77 | connection.on_query do |message| 78 | event = Event.new self, :message, Message.new(self, message) 79 | config.plugins.each_value &.handle(event) 80 | end 81 | 82 | connection.on(IRC::Message::NICK) do |message| 83 | if prefix = message.prefix 84 | event = Event.new self, :nick, user 85 | config.plugins.each_value &.handle(event) 86 | end 87 | end 88 | 89 | connection.on(IRC::Message::ERR_NICKCOLLISION, IRC::Message::ERR_NICKNAMEINUSE) do |message| 90 | if config.nickserv_regain? && config.password 91 | connection.await IRC::Message::RPL_WELCOME 92 | connection.send IRC::Message::PRIVMSG, "NickServ", "REGAIN #{config.nick} #{config.password}" 93 | end 94 | end 95 | 96 | connection.on(IRC::Message::KICK) do |message| 97 | channel, nick = message.parameters 98 | part channel if nick == user.nick 99 | end 100 | 101 | connection.connect 102 | 103 | channels.each do |channel| 104 | join channel 105 | end 106 | 107 | @started = true 108 | 109 | Signal::HUP.trap do 110 | config.reload 111 | end 112 | 113 | connection.on(IRC::Message::JOIN, IRC::Message::PART) do |message| 114 | if prefix = message.prefix 115 | type = message.type == IRC::Message::JOIN ? :join : :part 116 | channel = message.parameters.first 117 | user = User.from_mask(prefix, self) 118 | event = Event.new self, type, user, Channel.from_name(channel, self) 119 | config.plugins.each_value &.handle(event) 120 | 121 | part channel if message.type == IRC::Message::PART && user.nick == self.user.nick 122 | end 123 | end 124 | 125 | event = Event.new self, :connected 126 | config.plugins.each_value &.handle(event) 127 | 128 | connection.block 129 | end 130 | 131 | def filter?(event) 132 | @filters.any? &.call(event) 133 | end 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /framework/src/framework/channel.cr: -------------------------------------------------------------------------------- 1 | require "irc/message" 2 | 3 | require "./message" 4 | require "./bot" 5 | 6 | module Framework 7 | class Channel 8 | getter name 9 | getter context 10 | 11 | @@channels = {} of String => Channel 12 | 13 | def self.from_name(name : String, context) 14 | @@channels.fetch(name) { @@channels[name] = new(name, context) } 15 | end 16 | 17 | private def initialize(@name : String, @context : Bot) 18 | end 19 | 20 | def membership(user : User) 21 | membership user.nick 22 | end 23 | 24 | def membership(nick : String) 25 | channel = @context.connection.channels[@name] 26 | @context.connection.users.find_membership(IRC::Mask.parse(nick), channel) 27 | end 28 | 29 | def send(text : String) 30 | Message.new(@context, @name, text).send 31 | end 32 | 33 | def action(text : String) 34 | Message.new(@context, @name, text).as_action.send 35 | end 36 | 37 | def has?(user : User) 38 | has? user.nick 39 | end 40 | 41 | def has?(nick : String) 42 | !membership(nick).nil? 43 | end 44 | 45 | def opped?(user : User) 46 | opped? user.nick 47 | end 48 | 49 | def opped?(nick : String) 50 | membership(nick).try(&.op?) || false 51 | end 52 | 53 | def voiced?(user : User) 54 | voiced? user.nick 55 | end 56 | 57 | def voiced?(nick : String) 58 | membership(nick).try(&.voiced?) || false 59 | end 60 | 61 | def ban(user : User) 62 | ban user.mask 63 | end 64 | 65 | def ban(mask : IRC::Mask) 66 | ban mask.to_s 67 | end 68 | 69 | def ban(mask : String) 70 | mode mask, "+b" 71 | end 72 | 73 | def unban(user : User) 74 | unban user.mask 75 | end 76 | 77 | def unban(mask : IRC::Mask) 78 | unban mask.to_s 79 | end 80 | 81 | def unban(mask : String) 82 | mode mask, "-b" 83 | end 84 | 85 | def kick(user : User, reason=nil) 86 | kick user.nick, reason 87 | end 88 | 89 | def kick(nick : String, reason=nil) 90 | params = [@name, nick] 91 | params << reason if reason 92 | @context.connection.send IRC::Message::KICK, params 93 | end 94 | 95 | def op(user : User) 96 | op user.nick 97 | end 98 | 99 | def op(nick : String) 100 | mode nick, "+o" 101 | end 102 | 103 | def deop(user : User) 104 | deop user.nick 105 | end 106 | 107 | def deop(nick : String) 108 | mode nick, "-o" 109 | end 110 | 111 | def mode(param : String, mode : String) 112 | @context.connection.send IRC::Message::MODE, @name, mode, param 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /framework/src/framework/configuration.cr: -------------------------------------------------------------------------------- 1 | require "log" 2 | require "json" 3 | 4 | require "irc/connection" 5 | 6 | module Framework 7 | class Configuration 8 | class Error < Exception 9 | end 10 | 11 | module Plugin 12 | class ChannelList 13 | def self.new(pull : JSON::PullParser) 14 | case pull.kind 15 | when .null? 16 | default 17 | when .begin_array? 18 | new Array(String).new pull 19 | when .bool? 20 | if pull.read_bool == false 21 | none 22 | else 23 | raise Error.new "true is not a valid value for the channel list" 24 | end 25 | else 26 | raise Error.new "invalid channel list (#{pull.kind}" 27 | end 28 | end 29 | 30 | def self.default 31 | new true 32 | end 33 | 34 | def self.none 35 | new false 36 | end 37 | 38 | @channels : Array(String) 39 | 40 | def initialize(channels : Array(String) | Bool) 41 | case channels 42 | when Bool 43 | @wants_channel_messsages = channels 44 | @channels = [] of String 45 | when Array(String) 46 | @wants_channel_messsages = !channels.empty? 47 | @channels = channels 48 | else 49 | raise "bug" # prevent nilable 50 | end 51 | end 52 | 53 | def wants?(event : Event) 54 | if event.type == :message && event.message.channel? 55 | wants? event.message 56 | else 57 | true 58 | end 59 | end 60 | 61 | def wants?(message : Message) 62 | listens_to? message.channel 63 | end 64 | 65 | def listens_to?(channel) 66 | return false unless @wants_channel_messsages 67 | return true if @channels.empty? 68 | 69 | @channels.includes? channel.name 70 | end 71 | 72 | def add(channel : Channel) 73 | if @wants_channel_messsages 74 | unless @channels.empty? || @channels.includes?(channel.name) 75 | @channels << channel.name 76 | end 77 | else 78 | @wants_channel_messsages = true 79 | 80 | unless @channels.includes? channel.name 81 | @channels << channel.name 82 | end 83 | end 84 | end 85 | 86 | def remove(channel : Channel) 87 | if @channels.includes? channel.name 88 | @channels.delete channel.name 89 | if @channels.empty? 90 | @wants_channel_messsages = false 91 | end 92 | elsif @channels.empty? && @wants_channel_messsages 93 | @channels = channel.context.channels.dup 94 | @channels.delete channel.name 95 | end 96 | end 97 | 98 | def to_json(io) 99 | value = if @wants_channel_messsages 100 | if @channels.empty? 101 | nil 102 | else 103 | @channels 104 | end 105 | else 106 | false 107 | end 108 | 109 | value.to_json io 110 | end 111 | end 112 | 113 | macro included 114 | def self.empty 115 | obj = allocate 116 | obj.initialize_empty 117 | obj 118 | end 119 | end 120 | 121 | property! name : String 122 | delegate listens_to?, wants?, to: channels! 123 | 124 | def channels! 125 | @channels ||= ChannelList.default 126 | end 127 | 128 | def save(config) 129 | config.update_plugin_config name, JSON.parse(to_json) 130 | end 131 | end 132 | 133 | class Store 134 | include JSON::Serializable 135 | include JSON::Serializable::Strict 136 | 137 | LOG_LEVELS = { 138 | "debug" => ::Log::Severity::Debug, 139 | "info" => ::Log::Severity::Info, 140 | "warn" => ::Log::Severity::Warn, 141 | "error" => ::Log::Severity::Error, 142 | "fatal" => ::Log::Severity::Fatal, 143 | } 144 | 145 | LOG_LEVEL_NAMES = LOG_LEVELS.map { |k, v| {v, k} }.to_h 146 | 147 | property server : String 148 | property port : Int32? 149 | property channels : Array(String) 150 | property nick : String 151 | property user : String? 152 | @[JSON::Field(emit_null: true)] 153 | property password : String? 154 | property nickserv_regain : Bool? 155 | property realname : String? 156 | property ssl : Bool? 157 | property try_sasl : Bool? 158 | property log_level : String? 159 | property ignores : Array(String)? 160 | property plugins : Hash(String, JSON::Any)? 161 | 162 | def self.load_plugins(config, json) 163 | pull = JSON::PullParser.new json 164 | pull.on_key("plugins") do 165 | pull.read_object do |key| 166 | plugin_config = config.plugins[key]? 167 | if plugin_config 168 | plugin_config.read_config(pull) 169 | else 170 | pull.skip 171 | end 172 | end 173 | end 174 | end 175 | 176 | def plugins 177 | plugins = @plugins 178 | 179 | unless plugins.is_a? Hash(String, JSON::Any) 180 | plugins = Hash(String, JSON::Any).new 181 | @plugins = plugins 182 | end 183 | 184 | plugins 185 | end 186 | 187 | def plugins=(value : JSON::Any) 188 | @plugins = value 189 | end 190 | 191 | def to_json(config : Configuration) 192 | self.port = config.port 193 | self.channels = config.channels 194 | self.user = config.user 195 | self.password = config.password 196 | self.nickserv_regain = config.nickserv_regain? 197 | self.realname = config.realname 198 | self.ssl = config.ssl? 199 | self.try_sasl = config.try_sasl? 200 | self.log_level = LOG_LEVEL_NAMES[config.log_level] 201 | self.ignores = config.ignores 202 | 203 | to_pretty_json 204 | end 205 | 206 | def restore(config) 207 | raise Error.new("Unknown log level #{log_level}") unless log_level.nil? || LOG_LEVELS.has_key? log_level 208 | 209 | config.server = server 210 | config.port = port unless port.nil? 211 | config.channels = channels 212 | config.nick = nick 213 | config.user = user unless user.nil? 214 | config.password = password unless password.nil? 215 | config.nickserv_regain = nickserv_regain unless nickserv_regain.nil? 216 | config.realname = realname unless realname.nil? 217 | config.ssl = ssl unless ssl.nil? 218 | config.try_sasl = try_sasl unless try_sasl.nil? 219 | config.log_level = LOG_LEVELS[log_level].not_nil! unless log_level.nil? 220 | config.ignores = ignores unless ignores.nil? 221 | end 222 | end 223 | 224 | property! server : String? 225 | property port : Int32? 226 | property channels 227 | property! nick 228 | property! user : String? 229 | property password : String? 230 | property? nickserv_regain : Bool? 231 | property! realname : String? 232 | property? ssl : Bool? 233 | property? try_sasl : Bool? 234 | getter log_level : ::Log::Severity 235 | property! ignores : Array(String)? 236 | getter plugins 237 | 238 | @config_file : String? 239 | @store : Store? 240 | 241 | def initialize 242 | @plugins = Hash(String, PluginContainer::Workaround).new 243 | @channels = [] of String 244 | 245 | @nick = "CeBot" 246 | @user = "cebot" 247 | @password = nil 248 | @nickserv_regain = false 249 | @realname = "CeBot" 250 | @ssl = false 251 | @try_sasl = false 252 | @log_level = :info 253 | @ignores = [] of String 254 | 255 | self.log_level = @log_level 256 | end 257 | 258 | def port 259 | @port || (@ssl ? 6697 : 6667) 260 | end 261 | 262 | def add_plugin(plugin : PluginContainer) 263 | plugins[plugin.name] = plugin 264 | end 265 | 266 | def from_file(path) 267 | @config_file = path 268 | end 269 | 270 | def update_plugin_config(plugin, config) 271 | store = @store 272 | path = @config_file 273 | return unless store && path 274 | 275 | store.plugins[plugin] = config 276 | save 277 | end 278 | 279 | def log_level=(level : ::Log::Severity) 280 | @log_level = level 281 | 282 | backend = ::Log::IOBackend.new 283 | ::Log.builder.bind "irc.*", log_level, backend 284 | end 285 | 286 | def save 287 | store = @store 288 | path = @config_file 289 | return unless store && path 290 | 291 | json = store.to_json self 292 | File.write(path, json) 293 | end 294 | 295 | def load 296 | json = read_config 297 | store = Store.from_json(json) 298 | store.restore(self) 299 | @store = store 300 | Store.load_plugins self, json 301 | end 302 | 303 | def reload 304 | load 305 | end 306 | 307 | def to_connection 308 | load if @config_file 309 | 310 | IRC::Connection.build do |config| 311 | config.server = server 312 | config.port = port 313 | config.nick = nick 314 | config.user = user 315 | config.password = password 316 | config.realname = realname 317 | config.ssl = ssl? 318 | config.try_sasl = try_sasl? 319 | end 320 | end 321 | 322 | private def read_config 323 | path = @config_file 324 | raise Error.new("No configuration file defined") unless path 325 | File.read_lines(path).reject(&.match(/^\s*\/\//)).join 326 | end 327 | end 328 | end 329 | -------------------------------------------------------------------------------- /framework/src/framework/event.cr: -------------------------------------------------------------------------------- 1 | require "./bot" 2 | require "./message" 3 | require "./user" 4 | require "./channel" 5 | 6 | module Framework 7 | struct Event 8 | getter context 9 | getter type 10 | getter! sender : User 11 | getter! channel : Channel 12 | getter! message : Message 13 | 14 | def initialize(@context : Bot, @type : Symbol, @message : Message) 15 | @sender = message.sender 16 | @channel = message.channel? 17 | end 18 | 19 | def initialize(@context : Bot, @type : Symbol, @sender : User) 20 | end 21 | 22 | def initialize(@context : Bot, @type : Symbol, @sender : User, @channel : Channel) 23 | end 24 | 25 | def initialize(@context : Bot, @type : Symbol) 26 | end 27 | 28 | def to_s(io) 29 | io << "' 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /framework/src/framework/filter.cr: -------------------------------------------------------------------------------- 1 | require "./event" 2 | 3 | module Framework 4 | module Filter 5 | module Filter 6 | abstract def call(event : Event) #: Bool 7 | end 8 | 9 | struct NickFilter 10 | include Filter 11 | 12 | def initialize(@config : Configuration) 13 | end 14 | 15 | def call(event) 16 | return false unless event.sender? 17 | 18 | @config.ignores.includes? event.sender.nick 19 | end 20 | end 21 | 22 | alias Proc = Event -> Bool 23 | alias Item = Filter|Proc 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /framework/src/framework/json_store.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | module Framework 4 | class JsonStore(K, V) 5 | def initialize(@path : String) 6 | @data = Hash(K, V).new 7 | load 8 | end 9 | 10 | def fetch(key) 11 | yield @data[key]? 12 | end 13 | 14 | def modify(key) 15 | value = yield @data[key]? 16 | @data[key] = value 17 | write 18 | end 19 | 20 | def set(key, value) 21 | @data[key] = value 22 | write 23 | end 24 | 25 | def keys 26 | @data.keys 27 | end 28 | 29 | private def load 30 | if File.exists? @path 31 | @data = Hash(K, V).from_json(File.read(@path)) 32 | else 33 | Dir.mkdir_p File.dirname @path 34 | write 35 | end 36 | end 37 | 38 | private def write 39 | File.write @path, @data.to_json 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /framework/src/framework/limiter.cr: -------------------------------------------------------------------------------- 1 | module Framework 2 | class Limiter 3 | @time_span : Time::Span 4 | 5 | def initialize(@limit = 5, time_span = 60) 6 | @time_span = time_span.is_a?(Time::Span) ? time_span : Time::Span.new(seconds: time_span) 7 | @hits = [] of Time 8 | end 9 | 10 | # Returns true if the next hit would be allowed 11 | def pass? 12 | cleanup_hits 13 | 14 | @hits.size < @limit 15 | end 16 | 17 | # Returns true if the limit is exceeded 18 | # 19 | # This returns false in the border case, meaning the next hit could 20 | # exceed the limit 21 | def exceeded? 22 | cleanup_hits 23 | 24 | @hits.size > @limit 25 | end 26 | 27 | # Tracks a hit and returns true if that hit exceeded the limit 28 | def hit 29 | @hits << Time.local 30 | 31 | exceeded? 32 | end 33 | 34 | private def cleanup_hits 35 | now = Time.local 36 | @hits.reject! { |hit| now - hit > @time_span } 37 | end 38 | end 39 | 40 | struct LimiterCollection(T) 41 | def initialize(@limit = 5, @time_span = 60) 42 | @limiters = {} of T => Limiter 43 | end 44 | 45 | def pass?(key : T) 46 | fetch(key).pass? 47 | end 48 | 49 | def exceeded?(key : T) 50 | fetch(key).exceeded? 51 | end 52 | 53 | def hit(key : T) 54 | fetch(key).hit 55 | end 56 | 57 | private def fetch(key) 58 | @limiters.rehash 59 | @limiters.fetch(key) { @limiters[key] = Limiter.new(@limit, @time_span) } 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /framework/src/framework/message.cr: -------------------------------------------------------------------------------- 1 | require "irc/message" 2 | require "./user" 3 | require "./channel" 4 | require "./bot" 5 | 6 | module Framework 7 | class Message 8 | VALID_TYPES = %w(PRIVMSG NOTICE) 9 | 10 | getter target 11 | getter message 12 | getter sender : User 13 | getter context 14 | 15 | @channel : Channel? 16 | 17 | def initialize(@context : Bot, @target : String, @message : String, @type : Symbol|String = "PRIVMSG") 18 | @type = @type.to_s.upcase 19 | unless VALID_TYPES.includes? @type 20 | raise ArgumentError.new("Only valid types are #{VALID_TYPES.join(", ")}") 21 | end 22 | 23 | @sender = context.user 24 | end 25 | 26 | def initialize(@context : Bot, message = IRC::Message) 27 | @target, @message = message.parameters 28 | prefix = message.prefix 29 | if prefix 30 | @sender = User.from_mask(prefix, @context) 31 | else 32 | @sender = @context.user 33 | end 34 | @type = message.type 35 | @type = "PRIVMSG" unless VALID_TYPES.includes? @type 36 | end 37 | 38 | def as_action 39 | Message.new(@context, @target, "\001ACTION #{@message}\001") 40 | end 41 | 42 | def reply(text) 43 | target = @target.starts_with?('#') ? @target : @sender.nick 44 | Message.new(@context, target, text, @type).send 45 | end 46 | 47 | def send 48 | userhost = @context.user.mask.to_s 49 | nick = @context.user.nick 50 | prefix = "#{@type} #{@target} :" 51 | # 512 max message length according RFC, but Freenode only allows 510 52 | # -3 for :, ! and space 53 | # userhost fallback: hostname(63)+nickname(9)+@(1) = 73 54 | limit = 510 - 3 - nick.size - prefix.size - (userhost ? userhost.size : 73) 55 | 56 | @message.lines.each do |line| 57 | sent = 0 58 | while sent < line.size 59 | @context.connection.send "#{prefix}#{line[sent, limit]}" 60 | sent += limit 61 | end 62 | end 63 | end 64 | 65 | def channel? 66 | @channel ||= Channel.from_name(@target, @context) if @target.starts_with? '#' 67 | end 68 | 69 | def channel 70 | channel?.not_nil! 71 | end 72 | 73 | def to_s(io) 74 | io << "' 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /framework/src/framework/plugin.cr: -------------------------------------------------------------------------------- 1 | require "./configuration" 2 | require "./channel" 3 | require "./user" 4 | require "./timer" 5 | 6 | module Framework 7 | module Plugin 8 | macro config(properties) 9 | class Config 10 | {% for key, value in properties %} 11 | property {{key.id}} : {{value[:type]}} 12 | {% end %} 13 | 14 | def initialize_empty 15 | @channels = nil 16 | 17 | {% for key, value in properties %} 18 | @{{key.id}} = {{value[:default]}} 19 | {% end %} 20 | end 21 | end 22 | end 23 | 24 | macro included 25 | class Config 26 | include Framework::Configuration::Plugin 27 | include JSON::Serializable 28 | include JSON::Serializable::Strict 29 | 30 | @[JSON::Field(emit_null: true)] 31 | property channels : Framework::Configuration::Plugin::ChannelList? 32 | 33 | def initialize_empty 34 | @channels = ChannelList.default 35 | end 36 | end 37 | 38 | def self.config_class 39 | Config 40 | end 41 | 42 | @@matchers = [] of Regex 43 | def self.matchers 44 | @@matchers 45 | end 46 | 47 | @@events = [] of Symbol 48 | def self.events 49 | @@events 50 | end 51 | 52 | def self.config_loaded(config) 53 | end 54 | 55 | def initialize(@context : Framework::Bot, @config : Config) 56 | end 57 | end 58 | 59 | def self.config_class 60 | raise "Workaround issues mentioned in crystal-lang/crystal#2425" 61 | end 62 | 63 | def self.events 64 | raise "Workaround issues mentioned in crystal-lang/crystal#2425" 65 | end 66 | 67 | macro match(regex) 68 | matchers << {{regex}} 69 | end 70 | 71 | macro listen(event) 72 | events << {{event}} 73 | end 74 | 75 | getter context 76 | getter config 77 | 78 | def channel(name) 79 | Channel.from_name name, context 80 | end 81 | 82 | def user(name) 83 | User.from_nick name, context 84 | end 85 | 86 | def after(seconds, &block) 87 | Timer.new seconds, 1, &block 88 | end 89 | 90 | def every(seconds, limit=nil, &block) 91 | Timer.new seconds, limit, &block 92 | end 93 | 94 | def bot 95 | context.user 96 | end 97 | 98 | def logger 99 | context.logger 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /framework/src/framework/plugin_container.cr: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | require "./plugin" 4 | 5 | module Framework 6 | class PluginContainer(T, C) 7 | module Workaround; end 8 | include Workaround 9 | 10 | getter config : C? 11 | delegate channels, wants?, to: config 12 | 13 | def config 14 | (@config ||= T.config_class.empty).tap do |config| 15 | config.name = name 16 | end 17 | end 18 | 19 | def name 20 | T.to_s 21 | end 22 | 23 | def read_config(pull : JSON::PullParser) 24 | (@config = T.config_class.new pull).tap do |config| 25 | config.name = name 26 | T.config_loaded(config) 27 | end 28 | end 29 | 30 | def instance(context) 31 | T.new(context, config) 32 | end 33 | 34 | def handle(event) 35 | return unless wants? event 36 | return if filter? event 37 | 38 | if event.type == :message 39 | plugin = instance event.context 40 | handle_message event.message, plugin 41 | else 42 | return unless T.events.includes? event.type 43 | end 44 | 45 | plugin ||= instance event.context 46 | 47 | handle_event event, plugin 48 | end 49 | 50 | def filter?(event) 51 | event.context.filter? event 52 | end 53 | 54 | private def handle_event(event, plugin) 55 | plugin.react_to(event) if plugin.responds_to?(:react_to) 56 | rescue e 57 | puts "Couldn't run plugin #{self} for #{event}:" 58 | puts e 59 | puts e.backtrace.join("\n") 60 | end 61 | 62 | private def handle_message(message, plugin) 63 | T.matchers.each do |regex| 64 | match = message.message.match regex 65 | if match 66 | begin 67 | plugin.execute(message, match) if plugin.responds_to?(:execute) 68 | rescue e 69 | puts "Couldn't run plugin #{self} for #{message} matched by #{regex}:" 70 | puts e 71 | puts e.backtrace.join("\n") 72 | end 73 | break 74 | end 75 | end 76 | end 77 | end 78 | 79 | class PluginError < Exception 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /framework/src/framework/timer.cr: -------------------------------------------------------------------------------- 1 | module Framework 2 | class Timer 3 | def initialize(delay, limit=nil, &block) 4 | spawn do 5 | runs = 0 6 | loop do 7 | sleep delay 8 | block.call 9 | runs += 1 10 | break if limit && runs >= limit 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /framework/src/framework/user.cr: -------------------------------------------------------------------------------- 1 | require "./bot" 2 | 3 | module Framework 4 | class User 5 | getter irc_user 6 | getter context 7 | 8 | def initialize(irc_user : IRC::User, context : Bot) 9 | @irc_user = irc_user 10 | @context = context 11 | end 12 | 13 | delegate mask, realname, nick, user, host, modes, to: irc_user 14 | 15 | def self.from_nick(nick : String, context : Bot) 16 | from_mask nick, context 17 | end 18 | 19 | def self.from_mask(mask : String, context : Bot) 20 | from_mask IRC::Mask.parse(mask), context 21 | end 22 | 23 | def self.from_mask(mask : IRC::Mask, context : Bot) 24 | user = context.connection.users.find_user(mask) 25 | new user, context 26 | end 27 | 28 | def send(text : String) 29 | Message.new(@context, nick, text).send 30 | end 31 | 32 | def action(text : String) 33 | Message.new(@context, nick, text).as_action.send 34 | end 35 | 36 | def authname 37 | authname = irc_user.authname 38 | 39 | if authname.nil? 40 | context.connection.send IRC::Message::WHOIS, nick 41 | context.connection.await(IRC::Message::RPL_WHOISACCOUNT, 42 | IRC::Message::RPL_ENDOFWHOIS, 43 | IRC::Message::ERR_NOSUCHNICK) do |message| 44 | message.parameters[1] == nick 45 | end 46 | end 47 | 48 | irc_user.authname 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /irc/spec/message_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/irc/message" 3 | 4 | private def parses(raw, prefix, type, parameters) 5 | it "parses #{raw.inspect}" do 6 | message = IRC::Message.from raw 7 | message.should_not be_nil 8 | if message 9 | message.prefix.should eq prefix 10 | message.type.should eq type 11 | message.parameters.should eq parameters 12 | end 13 | end 14 | end 15 | 16 | describe IRC::Message do 17 | describe ".from" do 18 | parses "PRIVMSG ##cebot :foo bar", 19 | nil, IRC::Message::PRIVMSG, ["##cebot", "foo bar"] 20 | parses ":wilhelm.freenode.net 005 cebot CASEMAPPING=rfc1459 CHARSET=ascii NICKLEN=16 CHANNELLEN=50 TOPICLEN=390 ETRACE CPRIVMSG CNOTICE DEAF=D MONITOR=100 FNC TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR: :are supported by this server\r\n", 21 | "wilhelm.freenode.net", IRC::Message::ISUPPORT, ["cebot", "CASEMAPPING=rfc1459", "CHARSET=ascii", "NICKLEN=16", "CHANNELLEN=50", "TOPICLEN=390", "ETRACE", "CPRIVMSG", "CNOTICE", "DEAF=D", "MONITOR=100", "FNC", "TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR:", "are supported by this server"] 22 | parses "000 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 with spaces\r\n", 23 | nil, "000", ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15 with spaces"] 24 | parses "000 1 2 3 4 5 6 7 8 9 10 11 12 13 14 :15 with: spaces\n", 25 | nil, "000", ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15 with: spaces"] 26 | parses ":jhass!~jhass@jhass.eu MODE ##cebot +r", 27 | "jhass!~jhass@jhass.eu", IRC::Message::MODE, ["##cebot", "+r"] 28 | # Not RFC valid but Freenode sends it with a trailing space 29 | parses ":jhass!~jhass@jhass.eu MODE ##cebot +r ", 30 | "jhass!~jhass@jhass.eu", IRC::Message::MODE, ["##cebot", "+r"] 31 | parses ":jhass!jhass@000:0::1 PRIVMSG ##cebot :!roul", 32 | "jhass!jhass@000:0::1", IRC::Message::PRIVMSG, ["##cebot", "!roul"] 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /irc/spec/modes_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/irc/modes" 3 | 4 | def parse(modes) 5 | parsed = Array({Char, Char, String?}).new 6 | IRC::Modes::Parser.parse(modes) do |modifier, flag, parameter| 7 | parsed << {modifier, flag, parameter.as String?} 8 | end 9 | parsed 10 | end 11 | 12 | describe IRC::Modes::Parser do 13 | it "correctly parses mode lines" do 14 | parse("+t").should eq([{'+', 't', nil}]) 15 | parse("-t").should eq([{'-', 't', nil}]) 16 | parse("+v foo").should eq([{'+', 'v', "foo"}]) 17 | parse("-v foo").should eq([{'-', 'v', "foo"}]) 18 | parse("+t-v foo").should eq([{'+', 't', nil}, {'-', 'v', "foo"}]) 19 | parse("+vo foo bar").should eq([{'+', 'v', "foo"}, {'+', 'o', "bar"}]) 20 | parse("-vo foo bar").should eq([{'-', 'v', "foo"}, {'-', 'o', "bar"}]) 21 | parse("-tvo foo bar").should eq([{'-', 't', nil}, {'-', 'v', "foo"}, {'-', 'o', "bar"}]) 22 | parse("+vo-b foo bar baz").should eq([{'+', 'v', "foo"}, {'+', 'o', "bar"}, {'-', 'b', "baz"}]) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /irc/src/irc/channel.cr: -------------------------------------------------------------------------------- 1 | require "./membership" 2 | require "./message" 3 | require "./modes" 4 | 5 | module IRC 6 | # WITHOUT PARAMETER 7 | # CHANNELMODE - DESCRIPTION 8 | # +n - No external messages. Only channel members may talk in 9 | # the channel. 10 | # +t - Ops Topic. Only opped (+o) users may set the topic. 11 | # +s - Secret. Channel will not be shown in /whois and /list etc. 12 | # +p - Private. Disables /knock to the channel. 13 | # +m - Moderated. Only opped/voiced users may talk in channel. 14 | # +i - Invite only. Users need to be /invite'd or match a +I to 15 | # join the channel. 16 | # +r, +R - Registered users only. Only users identified to services 17 | # may join. 18 | # +r - The channel is registered (settable by services only) (UnrealIRCd) 19 | # +c, +S - No color. All color codes in messages are stripped. 20 | # +g - Free invite. Everyone may invite users. Significantly 21 | # weakens +i control. 22 | # +z - Op moderated. Messages blocked by +m, +b and +q are instead 23 | # sent to ops. 24 | # +L - Large ban list. Increase maximum number of +beIq entries. 25 | # Only settable by opers. 26 | # +P - Permanent. Channel does not disappear when empty. Only 27 | # settable by opers. 28 | # +F - Free target. Anyone may set forwards to this (otherwise 29 | # ops are necessary). 30 | # +Q - Disable forward. Users cannot be forwarded to the channel 31 | # (however, new forwards can still be set subject to +F). 32 | # +C - Disable CTCP. All CTCP messages to the channel, except ACTION, 33 | # are disallowed. 34 | # +A - Server/Net Admin only channel (settable by Admins) 35 | # +G - Filters out all Bad words in messages with [o] 36 | # +K - /KNOCK is not allowed [o] 37 | # +M - Must be using a registered nick (+r), or have voice access to talk [o] 38 | # +N - No Nickname changes are permitted in the channel [o] 39 | # +O - IRC Operator only channel (settable by IRCops) 40 | # +Q - No kicks allowed [o] 41 | # +T - No NOTICEs allowed in the channel [o] 42 | # +u - Auditorium mode (/names and /who #channel only show channel ops) [q] 43 | # +V - /INVITE is not allowed [o] 44 | # +z - Only Clients on a Secure Connection (SSL) can join [o] 45 | # +Z - All users on the channel are on a Secure connection (SSL) [server] 46 | # (This mode is set/unset by the server. Only if the channel is also +z) 47 | 48 | # WITH PARAMETER 49 | # CHANNELMODE - DESCRIPTION 50 | # +f - Forward. Forwards users who cannot join because of +i, (Freenode) 51 | # +j, +l or +r. 52 | # PARAMS: /mode #channel +f #channel2 53 | # +f - Flood protection (for more info see /HELPOP CHMODEF) [o] (UnrealIRCD) 54 | # +j - Join throttle. Limits number of joins to the channel per time. 55 | # PARAMS: /mode #channel +j count:time 56 | # +k - Key. Requires users to issue /join #channel KEY to join. 57 | # PARAMS: /mode #channel +k key 58 | # +l - Limit. Impose a maximum number of LIMIT people in the channel. 59 | # PARAMS: /mode #channel +l limit 60 | # +L - Channel link (If +l is full, the next user will auto-join ) [q] 61 | class Channel 62 | MEMBERHSIP_MODES = {'v', 'b', 'q', 'e', 'o', 'I', 'h', 'a'} 63 | 64 | getter name 65 | getter modes 66 | 67 | def initialize(@connection : Connection, @name : String) 68 | @modes = Modes.new 69 | @message_handlers = [] of Message -> 70 | 71 | @connection.on Message::PRIVMSG, Message::NOTICE do |message| 72 | target = message.parameters.first 73 | @message_handlers.each &.call(message) if target == @name 74 | end 75 | 76 | @connection.on Message::MODE do |message| 77 | target = message.parameters.first 78 | modes = message.parameters[1..-1].join(' ') 79 | 80 | if target == @name 81 | Modes::Parser.parse(modes) do |modifier, flag, parameter| 82 | unless MEMBERHSIP_MODES.includes?(flag) 83 | if modifier == '+' 84 | @modes.set flag, parameter 85 | else 86 | @modes.unset flag, parameter 87 | end 88 | end 89 | end 90 | end 91 | end 92 | end 93 | 94 | def on_message(&block : Message ->) 95 | @message_handlers << block 96 | end 97 | 98 | def clear_handlers 99 | @message_handlers.clear 100 | end 101 | 102 | def join 103 | @connection.send "JOIN #{@name}" 104 | end 105 | 106 | def part 107 | @connection.send "PART #{@name}" 108 | end 109 | 110 | {% for item in [{'b', "banned"}, {'q', "quieted"}, {'e', "exempted"}, {'I', "invited"}] %} 111 | {% flag = item[0] %} 112 | {% name = item[1] %} 113 | def query_{{name.id}} 114 | end 115 | {% end %} 116 | 117 | {% for item in [{['n'], "no_external"}, {['t'], "topic_locked"}, {['s'], "secret"}, 118 | {['p'], "pivate"}, {['m'], "moderated"}, {['i'], "invite_only"}, 119 | {['r', 'R'], "registered_only"}, {['c', 'S'], "no_colors"}, {['g'], "free_invite"}, 120 | {['z'], "reduced_moderation"}, {['L'], "large_banlist"}, {['P'], "permanent"}, 121 | {['F'], "free_target"}, {['Q'], "disabled_forward"}, {['C'], "no_ctcp"}, 122 | {['A'], "admin_channel"}, {['K'], "no_knock"}, {['N'], "no_nick_changes"}, 123 | {['O'], "operator_channel"}, {['V'], "no_invite"}, {['z'], "secure_only"}, 124 | {['Q'], "no_kicks"}, {['T'], "no_notices"}, {['u'], "auditorium"}, 125 | {['Z'], "all_secure"}] %} 126 | {% flags = item[0] %} 127 | {% name = item[1] %} 128 | def {{name.id}}? 129 | {% for flag in flags %} 130 | @modes.set?({{flag}}) || 131 | {% end %} 132 | false # Cheat post fence problem 133 | end 134 | {% end %} 135 | 136 | {% for item in [{'j', "join_throttle"}, {'k', "key"}, {'l', "limit"}, {'L', "link"}] %} 137 | {% flag = item[0] %} 138 | {% name = item[1] %} 139 | def {{name.id}} 140 | @modes.get {{flag}} 141 | end 142 | {% end %} 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /irc/src/irc/connection.cr: -------------------------------------------------------------------------------- 1 | require "socket" 2 | require "signal" 3 | require "base64" 4 | require "openssl" 5 | 6 | require "log" 7 | 8 | require "./channel" 9 | require "./mask" 10 | require "./message" 11 | require "./network" 12 | require "./user_manager" 13 | require "./workers" 14 | 15 | module IRC 16 | Log = ::Log.for("irc") 17 | 18 | class Connection 19 | Log = IRC::Log.for("connection") 20 | 21 | class Config 22 | property! server : String? 23 | property port 24 | property nick 25 | property user 26 | property! password : String? 27 | property realname 28 | property! ssl : Bool? 29 | property! try_sasl : Bool? 30 | 31 | private def initialize 32 | @port = 6667 33 | @nick = "Crystal" 34 | @user = "crystal" 35 | @password = nil 36 | @realname = "Crystal IRC" 37 | @ssl = false 38 | @try_sasl = false 39 | end 40 | 41 | def self.new(server : String) 42 | build.tap do |config| 43 | config.server = server 44 | end 45 | end 46 | 47 | def self.build 48 | new.tap do |config| 49 | yield config 50 | 51 | raise ArgumentError.new "server must be provided" unless config.server? 52 | 53 | if config.try_sasl? && !config.password? 54 | raise ArgumentError.new "must set password when enabling SASL" 55 | end 56 | 57 | config.port = config.ssl ? 6697 : 6667 58 | end 59 | end 60 | 61 | def password? 62 | password = @password 63 | !password.nil? && !password.empty? 64 | end 65 | end 66 | 67 | property? connected 68 | getter config 69 | getter users 70 | getter channels 71 | getter network 72 | 73 | @workers : {Processor, Reader, Sender}? 74 | 75 | def self.build(&block : Config ->) 76 | new Config.build(&block) 77 | end 78 | 79 | def self.new(server : String) 80 | new Config.new(server) 81 | end 82 | 83 | def initialize(@config : Config) 84 | @send_queue = ::Channel(String).new(64) 85 | @users = UserManager.new 86 | @channels = {} of String => Channel 87 | @processor = Processor.new 88 | @network = Network.new 89 | @connected = false 90 | @exit_channel = ::Channel(Int32).new 91 | 92 | @users.track Mask.parse(@config.nick) # Track self with pseudo mask 93 | end 94 | 95 | def await(*types, &callback : Message -> Bool) 96 | channel = ::Channel(Message).new 97 | handler = @processor.on(*types) do |message| 98 | channel.send(message) if callback.call(message) 99 | end 100 | 101 | channel.receive.tap do 102 | @processor.handlers.delete(handler) 103 | @processor.handle_others 104 | end 105 | end 106 | 107 | def await(*types) 108 | await(*types) do |_message| 109 | true 110 | end 111 | end 112 | 113 | def send(message : Message) 114 | @send_queue.send message.to_s 115 | end 116 | 117 | def send(type : String, parameters : Array(String)) 118 | send message_for(type, parameters) 119 | end 120 | 121 | def send(type : String, *parameters) 122 | # Compiler bug: get rid of NoReturn the manual way 123 | converted_parameters = [] of String 124 | parameters.each do |parameter| 125 | converted_parameters << parameter.to_s 126 | end 127 | send message_for(type, converted_parameters) 128 | end 129 | 130 | private def message_for(type, parameters) 131 | if parameters.empty? 132 | message = Message.from(type) 133 | unless message 134 | raise Message::Malformed.new "#{type} does not parse as an IRC message" 135 | end 136 | else 137 | message = Message.new(type, parameters) 138 | end 139 | 140 | message 141 | end 142 | 143 | def nick=(nick : String) 144 | oldnick = config.nick 145 | config.nick = nick 146 | @users.nick Mask.parse(oldnick), nick unless nick == oldnick 147 | send Message::NICK, nick unless connected? && nick == oldnick 148 | end 149 | 150 | def join(channel_name) 151 | Channel.new(self, channel_name).tap do |channel| 152 | channel.join 153 | @users.join Mask.parse(config.nick), channel 154 | @channels[channel_name] = channel 155 | end 156 | end 157 | 158 | def part(channel_name) 159 | @channels[channel_name]?.tap do |channel| 160 | if channel 161 | channel.part 162 | @users.part Mask.parse(config.nick), channel 163 | channel.clear_handlers 164 | @channels.delete channel_name 165 | end 166 | end 167 | end 168 | 169 | def on(*args, &handler : Message ->) 170 | @processor.on *args, &handler 171 | end 172 | 173 | def on_query(&handler : Message ->) 174 | on(Message::PRIVMSG, Message::NOTICE) do |message| 175 | target = message.parameters.first 176 | handler.call(message) if target == config.nick 177 | end 178 | end 179 | 180 | def connect 181 | Log.info { "Connecting to #{config.server}:#{config.port}#{" (SSL enabled)" if config.ssl?}" } 182 | 183 | socket = TCPSocket.new config.server, config.port 184 | socket.read_timeout = 300 185 | socket.write_timeout = 5 186 | socket.keepalive = true 187 | 188 | socket = OpenSSL::SSL::Socket::Client.new socket if config.ssl? 189 | 190 | send Message::CAP, "LS" 191 | 192 | on Message::CAP do |cap| 193 | case cap.parameters[1] 194 | when "LS" 195 | capabilities = cap.parameters.last.split(' ') 196 | 197 | send Message::CAP, "REQ", "account-notify" if capabilities.includes? "account-notify" 198 | send Message::CAP, "REQ", "extended-join" if capabilities.includes? "extended-join" 199 | 200 | if config.try_sasl? && capabilities.includes? "sasl" 201 | Log.info { "Attempting SASL authentication" } 202 | send Message::CAP, "REQ", "sasl" 203 | else 204 | send Message::CAP, "END" 205 | end 206 | when "ACK" 207 | network.account_notify = true if cap.parameters.last == "account-notify" 208 | network.extended_join = true if cap.parameters.last == "extended-join" 209 | 210 | if cap.parameters.last == "sasl" 211 | send Message::AUTHENTICATE, "PLAIN" 212 | end 213 | when "NAK" 214 | else 215 | send Message::CAP, "END" 216 | end 217 | end 218 | 219 | on Message::AUTHENTICATE do 220 | send Message::AUTHENTICATE, Base64.strict_encode("#{config.nick}\0#{config.nick}\0#{config.password}") 221 | end 222 | 223 | on(Message::RPL_LOGGEDIN, Message::RPL_LOGGEDOUT, Message::ERR_NICKLOCKED, 224 | Message::RPL_SASLSUCCESS, Message::ERR_SASLFAIL, Message::ERR_SASLTOOLONG, 225 | Message::RPL_SASL_ABORTED, Message::ERR_SASLALREADY) do |message| 226 | if {Message::RPL_LOGGEDIN, Message::RPL_SASLSUCCESS, Message::ERR_SASLALREADY}.includes? message.type 227 | Log.info { "SASL authentication succeeded" } 228 | else 229 | Log.warn { "SASL authentication failed" } 230 | end 231 | 232 | send Message::CAP, "END" 233 | end 234 | 235 | if config.password? && !config.try_sasl? 236 | send Message::PASS, config.password 237 | end 238 | 239 | self.nick = config.nick 240 | send Message::USER, config.user, "0", "*", config.realname 241 | 242 | Signal::INT.trap do 243 | quit 244 | end 245 | 246 | Signal::TERM.trap do 247 | quit 248 | end 249 | 250 | on Message::ERROR do |error| 251 | if error.message.starts_with? "Closing Link" 252 | Log.warn { "Server closed connection (#{error.message}), shutting down" } 253 | 254 | stop_workers 255 | exit 1 256 | end 257 | end 258 | 259 | on Message::PING do |ping| 260 | send Message::PONG, ping.message 261 | end 262 | 263 | on Message::PRIVMSG do |message| 264 | if message.message == "\u0001VERSION\u0001" 265 | send Message::PRIVMSG, message.prefix.not_nil!.split("!", 2).first, "#{config.user} 0.1.0" 266 | end 267 | end 268 | 269 | on Message::ERR_NICKCOLLISION, Message::ERR_NICKNAMEINUSE, Message::ERR_UNAVAILRESOURCE do |error| 270 | if error.type != Message::ERR_UNAVAILRESOURCE || error.message == config.nick 271 | self.nick = "#{config.nick}_" 272 | end 273 | end 274 | 275 | on Message::RPL_WELCOME do |message| 276 | self.connected = true 277 | self.nick = message.parameters.first 278 | end 279 | 280 | @users.register_handlers self 281 | 282 | processor = @processor.not_nil! 283 | reader = Reader.new socket, processor.channel, @send_queue 284 | sender = Sender.new socket, @send_queue 285 | 286 | @workers = {processor, reader, sender} 287 | 288 | await(Message::RPL_WELCOME) 289 | 290 | Log.info { "Connected" } 291 | end 292 | 293 | def quit(message = "Crystal IRC") 294 | send Message::QUIT, message 295 | @processor.handle_others 296 | stop_workers 297 | exit 298 | end 299 | 300 | def exit(code = 0) 301 | @exit_channel.send code 302 | @exit_channel.close 303 | @processor.handle_others 304 | Fiber.yield 305 | end 306 | 307 | def block 308 | ::exit @exit_channel.receive 309 | end 310 | 311 | private def stop_workers 312 | @workers.not_nil!.each &.stop 313 | end 314 | end 315 | 316 | class CommandFailed < Exception 317 | end 318 | end 319 | -------------------------------------------------------------------------------- /irc/src/irc/mask.cr: -------------------------------------------------------------------------------- 1 | module IRC 2 | class Mask 3 | property! nick 4 | property user 5 | property host 6 | def_equals_and_hash nick 7 | 8 | def self.parse(mask) 9 | if mask.includes? '@' 10 | local, host = mask.split '@' 11 | else 12 | local = mask 13 | end 14 | 15 | if local.includes? '!' 16 | nick, user = local.split '!' 17 | else 18 | nick = local 19 | end 20 | 21 | new nick, user, host 22 | end 23 | 24 | def initialize(@nick : String, @user : String?, @host : String?) 25 | end 26 | 27 | def update(mask) 28 | if mask.nick == @nick 29 | @user = mask.user if mask.user 30 | @host = mask.host if mask.host 31 | end 32 | end 33 | 34 | def matches?(other) 35 | return true if !has_wildcard? && nick == other.nick && user == other.user && host == other.host 36 | return false unless has_wildcard? 37 | 38 | !to_regex.match(other).nil? 39 | end 40 | 41 | def has_wildcard? 42 | @has_wildcards ||= [@nick, @user, @host].any? {|part| 43 | part && contains_wildcard?(part) 44 | } 45 | end 46 | 47 | def to_regex 48 | @regex = Regex.new(Regex.escape(to_s)) unless has_wildcard? 49 | return @regex if @regex 50 | 51 | wildcard = Regex.escape(to_s) 52 | wildcard = wildcard.gsub(/(? - Gives Voice to the user (May talk if chan is +m) 23 | # +h - Gives HalfOp status to the user (Limited op access) 24 | # +a - Gives Channel Admin to the user 25 | 26 | class Membership 27 | getter channel 28 | getter? active 29 | getter modes 30 | 31 | def initialize(@channel : Channel) 32 | @active = false 33 | @modes = Modes.new 34 | end 35 | 36 | def join 37 | @active = true 38 | end 39 | 40 | def part 41 | @active = false 42 | @modes.unset 'v' 43 | @modes.unset 'o' 44 | @modes.unset 'h' 45 | @modes.unset 'a' 46 | end 47 | 48 | def mode(flag, gained, parameter=nil) 49 | if gained 50 | @modes.set flag, parameter 51 | else 52 | @modes.unset flag, parameter 53 | end 54 | end 55 | 56 | {% for item in [{'v', "voiced"}, {'o', "op"}, {'q', "owner"}, {'h', "halfop"}, {'a', "admin"}] %} 57 | {% flag = item[0] %} 58 | {% name = item[1] %} 59 | def {{name.id}}? 60 | @modes.set? {{flag}} 61 | end 62 | {% end %} 63 | 64 | {% for item in [{'b', "banned"}, {'q', "quieted"}, {'e', "exempted"}, {'I', "invited"}] %} 65 | {% flag = item[0] %} 66 | {% name = item[1] %} 67 | def {{name.id}}? 68 | set = @modes.set? {{flag}}, false 69 | return set if set 70 | 71 | channel.query_{{name.id}} 72 | @modes.set? {{flag}}, false 73 | end 74 | {% end %} 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /irc/src/irc/message.cr: -------------------------------------------------------------------------------- 1 | module IRC 2 | class Message 3 | PASS = "PASS" 4 | NICK = "NICK" 5 | USER = "USER" 6 | OPER = "OPER" 7 | MODE = "MODE" 8 | SERVICE = "SERVICE" 9 | QUIT = "QUIT" 10 | SQUIT = "SQUIT" 11 | JOIN = "JOIN" 12 | PART = "PART" 13 | TOPIC = "TOPIC" 14 | NAMES = "NAMES" 15 | LIST = "LIST" 16 | INVITE = "INVITE" 17 | KICK = "KICK" 18 | PRIVMSG = "PRIVMSG" 19 | NOTICE = "NOTICE" 20 | MOTD = "MOTD" 21 | LUSERS = "LUSERS" 22 | VERSION = "VERSION" 23 | STATS = "STATS" 24 | LINKS = "LINKS" 25 | TIME = "TIME" 26 | CONNECT = "CONNECT" 27 | TRACE = "TRACE" 28 | ADMIN = "ADMIN" 29 | INFO = "INFO" 30 | SERVLIST = "SERVLIST" 31 | SQUERY = "SQUERY" 32 | WHO = "WHO" 33 | WHOIS = "WHOIS" 34 | WHOWAS = "WHOWAS" 35 | KILL = "KILL" 36 | PING = "PING" 37 | PONG = "PONG" 38 | ERROR = "ERROR" 39 | AWAY = "AWAY" 40 | REHASH = "REHASH" 41 | DIE = "DIE" 42 | RESTART = "RESTART" 43 | SUMMON = "SUMMON" 44 | USERS = "USERS" 45 | WALLOPS = "WALLOPS" 46 | USERHOST = "USERHOST" 47 | ISON = "ISON" 48 | 49 | CAP = "CAP" 50 | 51 | AUTHENTICATE = "AUTHENTICATE" 52 | RPL_LOGGEDIN = "900" 53 | RPL_LOGGEDOUT = "901" 54 | ERR_NICKLOCKED = "902" 55 | RPL_SASLSUCCESS = "903" 56 | ERR_SASLFAIL = "904" 57 | ERR_SASLTOOLONG = "905" 58 | RPL_SASL_ABORTED = "906" 59 | ERR_SASLALREADY = "907" 60 | RPL_SASLMECHS = "908" 61 | 62 | ACCOUNT = "ACCOUNT" 63 | RPL_WHOISACCOUNT = "330" 64 | 65 | RPL_BOUNCE = "005" # According to RFC 66 | ISUPPORT = "005" # In practice 67 | 68 | RPL_WELCOME = "001" 69 | RPL_YOURHOST = "002" 70 | RPL_CREATED = "003" 71 | RPL_MYINFO = "004" 72 | RPL_USERHOST = "302" 73 | RPL_ISON = "303" 74 | RPL_AWAY = "301" 75 | RPL_UNAWAY = "305" 76 | RPL_NOWAWAY = "306" 77 | RPL_WHOISUSER = "311" 78 | RPL_WHOISSERVER = "312" 79 | RPL_WHOISOPERATOR = "313" 80 | RPL_WHOISIDLE = "317" 81 | RPL_ENDOFWHOIS = "318" 82 | RPL_WHOISCHANNELS = "319" 83 | RPL_WHOWASUSER = "314" 84 | RPL_ENDOFWHOWAS = "369" 85 | RPL_LISTSTART = "321" 86 | RPL_LIST = "322" 87 | RPL_LISTEND = "323" 88 | RPL_UNIQOPIS = "325" 89 | RPL_CHANNELMODEIS = "324" 90 | RPL_NOTOPIC = "331" 91 | RPL_TOPIC = "332" 92 | RPL_INVITING = "341" 93 | RPL_SUMMONING = "342" 94 | RPL_INVITELIST = "346" 95 | RPL_ENDOFINVITELIST = "347" 96 | RPL_EXCEPTLIST = "348" 97 | RPL_ENDOFEXCEPTLIST = "349" 98 | RPL_VERSION = "351" 99 | RPL_WHOREPLY = "352" 100 | RPL_ENDOFWHO = "315" 101 | RPL_NAMREPLY = "353" 102 | RPL_ENDOFNAMES = "366" 103 | RPL_LINKS = "364" 104 | RPL_ENDOFLINKS = "365" 105 | RPL_BANLIST = "367" 106 | RPL_ENDOFBANLIST = "368" 107 | RPL_INFO = "371" 108 | RPL_ENDOFINFO = "374" 109 | RPL_MOTDSTART = "375" 110 | RPL_MOTD = "372" 111 | RPL_ENDOFMOTD = "376" 112 | RPL_YOUREOPER = "381" 113 | RPL_REHASHING = "382" 114 | RPL_YOURESERVICE = "383" 115 | RPL_TIME = "391" 116 | RPL_USERSSTART = "392" 117 | RPL_USERS = "393" 118 | RPL_ENDOFUSERS = "394" 119 | RPL_NOUSERS = "395" 120 | RPL_TRACELINK = "200" 121 | RPL_TRACECONNECTING = "201" 122 | RPL_TRACEHANDSHAKE = "202" 123 | RPL_TRACEUNKNOWN = "203" 124 | RPL_TRACEOPERATOR = "204" 125 | RPL_TRACEUSER = "205" 126 | RPL_TRACESERVER = "206" 127 | RPL_TRACESERVICE = "207" 128 | RPL_TRACENEWTYPE = "208" 129 | RPL_TRACECLASS = "209" 130 | RPL_TRACERECONNECT = "210" 131 | RPL_TRACELOG = "261" 132 | RPL_TRACEEND = "262" 133 | RPL_STATSLINKINFO = "211" 134 | RPL_STATSCOMMANDS = "212" 135 | RPL_ENDOFSTATS = "219" 136 | RPL_STATSUPTIME = "242" 137 | RPL_STATSOLINE = "243" 138 | RPL_UMODEIS = "221" 139 | RPL_SERVLIST = "234" 140 | RPL_SERVLISTEND = "235" 141 | RPL_LUSERCLIENT = "251" 142 | RPL_LUSEROP = "252" 143 | RPL_LUSERUNKNOWN = "253" 144 | RPL_LUSERCHANNELS = "254" 145 | RPL_LUSERME = "255" 146 | RPL_ADMINME = "256" 147 | RPL_ADMINLOC1 = "257" 148 | RPL_ADMINLOC2 = "258" 149 | RPL_ADMINEMAIL = "259" 150 | RPL_TRYAGAIN = "263" 151 | ERR_NOSUCHNICK = "401" 152 | ERR_NOSUCHSERVER = "402" 153 | ERR_NOSUCHCHANNEL = "403" 154 | ERR_CANNOTSENDTOCHAN = "404" 155 | ERR_TOOMANYCHANNELS = "405" 156 | ERR_WASNOSUCHNICK = "406" 157 | ERR_TOOMANYTARGETS = "407" 158 | ERR_NOSUCHSERVICE = "408" 159 | ERR_NOORIGIN = "409" 160 | ERR_NORECIPIENT = "411" 161 | ERR_NOTEXTTOSEND = "412" 162 | ERR_NOTOPLEVEL = "413" 163 | ERR_WILDTOPLEVEL = "414" 164 | ERR_BADMASK = "415" 165 | ERR_TOOMANYMATCHES = "416" 166 | ERR_UNKNOWNCOMMAND = "421" 167 | ERR_NOMOTD = "422" 168 | ERR_NOADMININFO = "423" 169 | ERR_FILEERROR = "424" 170 | ERR_NONICKNAMEGIVEN = "431" 171 | ERR_ERRONEUSNICKNAME = "432" 172 | ERR_NICKNAMEINUSE = "433" 173 | ERR_NICKCOLLISION = "436" 174 | ERR_UNAVAILRESOURCE = "437" 175 | ERR_USERNOTINCHANNEL = "441" 176 | ERR_NOTONCHANNEL = "442" 177 | ERR_USERONCHANNEL = "443" 178 | ERR_NOLOGIN = "444" 179 | ERR_SUMMONDISABLED = "445" 180 | ERR_USERSDISABLED = "446" 181 | ERR_NOTREGISTERED = "451" 182 | ERR_NEEDMOREPARAMS = "461" 183 | ERR_ALREADYREGISTRED = "462" 184 | ERR_NOPERMFORHOST = "463" 185 | ERR_PASSWDMISMATCH = "464" 186 | ERR_YOUREBANNEDCREEP = "465" 187 | ERR_YOUWILLBEBANNED = "466" 188 | ERR_KEYSET = "467" 189 | ERR_CHANNELISFULL = "471" 190 | ERR_UNKNOWNMODE = "472" 191 | ERR_INVITEONLYCHAN = "473" 192 | ERR_BANNEDFROMCHAN = "474" 193 | ERR_BADCHANNELKEY = "475" 194 | ERR_BADCHANMASK = "476" 195 | ERR_NOCHANMODES = "477" 196 | ERR_BANLISTFULL = "478" 197 | ERR_NOPRIVILEGES = "481" 198 | ERR_CHANOPRIVSNEEDED = "482" 199 | ERR_CANTKILLSERVER = "483" 200 | ERR_RESTRICTED = "484" 201 | ERR_UNIQOPPRIVSNEEDED = "485" 202 | ERR_NOOPERHOST = "491" 203 | ERR_UMODEUNKNOWNFLAG = "501" 204 | ERR_USERSDONTMATCH = "502" 205 | 206 | def self.from(message) 207 | prefix, type, parameters = parse(message) 208 | 209 | case type 210 | when "213", "214", "215", "216", "217", "218", "231", 211 | "232", "233", "240", "241", "244", "245", "246", 212 | "247", "250", "300", "316", "361", "362", "363", 213 | "373", "384", "492" 214 | # Reserved but not in official use 215 | else 216 | new(prefix, type, parameters) 217 | end 218 | rescue e : Malformed 219 | end 220 | 221 | private def self.parse(message) 222 | prefix = String::Builder.new 223 | type = String::Builder.new 224 | parameters = [] of String 225 | parameter = String::Builder.new 226 | state = :start 227 | 228 | message.each_char do |char| 229 | case state 230 | when :prefix, :start 231 | case char 232 | when ':' 233 | if state == :prefix 234 | prefix << char 235 | else 236 | state = :prefix 237 | end 238 | when ' ' 239 | state = :type 240 | when '\r', '\n' 241 | raise Malformed.new 242 | else 243 | if state == :start 244 | state = :type 245 | type << char 246 | else 247 | prefix << char 248 | end 249 | end 250 | when :type 251 | case char 252 | when ' ' 253 | state = :parameter 254 | when '\r', '\n' 255 | raise Malformed.new 256 | else 257 | type << char 258 | end 259 | when :parameter 260 | case char 261 | when ':' 262 | if parameter.empty? # Only trail if preceded by space 263 | state = :trail 264 | else 265 | parameter << char 266 | end 267 | when '\r', '\n' 268 | when ' ' 269 | parameters << parameter.to_s 270 | parameter = String::Builder.new 271 | state = :trail if parameters.size == 14 272 | else 273 | parameter << char 274 | end 275 | when :trail 276 | case char 277 | when '\r', '\n' 278 | when ':' 279 | parameter << char unless parameter.empty? # skip leading : 280 | else 281 | parameter << char 282 | end 283 | end 284 | end 285 | 286 | # Freenode violates the RFC and may send us trailing whitespace 287 | parameter = parameter.to_s.strip 288 | parameters << parameter unless parameter.empty? 289 | 290 | prefix = prefix.to_s 291 | prefix = nil if prefix.empty? 292 | 293 | if state == :trail || state == :parameter 294 | {prefix, type.to_s, parameters} 295 | else 296 | raise Malformed.new 297 | end 298 | end 299 | 300 | getter prefix 301 | getter type 302 | getter parameters 303 | 304 | def initialize(@prefix : String?, @type : String, @parameters : Array(String)) 305 | end 306 | 307 | def initialize(@type : String, @parameters : Array(String)) 308 | end 309 | 310 | def message 311 | case type 312 | when PRIVMSG, NOTICE 313 | parameters.last 314 | else 315 | parameters.first 316 | end 317 | end 318 | 319 | def mask 320 | Mask.parse prefix.not_nil! 321 | end 322 | 323 | def to_s(io) 324 | io << ':' << @prefix << ' ' if @prefix 325 | io << @type 326 | parameters = @parameters.dup 327 | last = parameters.pop { nil } 328 | 329 | parameters.each do |parameter| 330 | io << ' ' 331 | io << parameter 332 | end 333 | 334 | if last 335 | io << ' ' 336 | io << ':' if last.includes?(' ') || last.includes?(' ') 337 | io << last 338 | end 339 | 340 | io << "\r\n" unless last && last.ends_with? '\n' 341 | end 342 | 343 | class Malformed < Exception 344 | end 345 | end 346 | end 347 | -------------------------------------------------------------------------------- /irc/src/irc/modes.cr: -------------------------------------------------------------------------------- 1 | module IRC 2 | class Modes 3 | module Parser 4 | def self.parse(modes) 5 | parameters = modes.split(" ") 6 | modes = seperate_flags parameters.shift? || "" 7 | 8 | (modes.size-parameters.size).times do 9 | mode = modes.shift 10 | modifier, flag = mode 11 | 12 | yield modifier, flag, nil 13 | end 14 | 15 | parameters.each do |parameter| 16 | modifier, flag = modes.shift 17 | 18 | yield modifier, flag, parameter 19 | end 20 | end 21 | 22 | private def self.seperate_flags(modes) 23 | modifier = '+' 24 | separated = [] of {Char, Char} 25 | 26 | modes.chars.each do |flag| 27 | case flag 28 | when '+', '-' 29 | modifier = flag 30 | else 31 | separated << {modifier, flag} 32 | end 33 | end 34 | 35 | separated 36 | end 37 | end 38 | 39 | record Flag, flag : Char, parameter : String? 40 | 41 | include Enumerable(Flag) 42 | 43 | def initialize(modes="") 44 | @plain_flags = Set(Flag).new 45 | @parameterized_flags = Set(Flag).new 46 | parse modes 47 | end 48 | 49 | def parse(modes) 50 | Parser.parse(modes) do |modifier, flag, parameter| 51 | if modifier == '+' 52 | set flag, parameter 53 | else 54 | unset flag, parameter 55 | end 56 | end 57 | end 58 | 59 | def get(flag : Char) 60 | @parameterized_flags.find {|item| item.flag == flag }.try(&.parameter) 61 | end 62 | 63 | def set(flag : Char, parameter=nil) 64 | mode = Flag.new(flag, parameter) 65 | 66 | if parameter 67 | @parameterized_flags << mode 68 | else 69 | @plain_flags << mode 70 | end 71 | end 72 | 73 | def unset(flag : Char, parameter=nil) 74 | mode = Flag.new(flag, parameter) 75 | 76 | if parameter 77 | @parameterized_flags.delete(mode) 78 | else 79 | @plain_flags.delete(mode) 80 | end 81 | end 82 | 83 | # pass false as parameter to look only at the flag but only in the 84 | # parameterized flags 85 | def set?(flag : Char, parameter=nil) 86 | mode = Flag.new(flag, parameter) 87 | 88 | if parameter 89 | @parameterized_flags.includes? mode 90 | elsif parameter == false 91 | @parameterized_flags.any? &.flag==(flag) 92 | else 93 | @plain_flags.includes? mode 94 | end 95 | end 96 | 97 | def each 98 | (@plain_flags | @parameterized_flags).each do |flag| 99 | yield flag 100 | end 101 | end 102 | 103 | def to_s(io) 104 | io << "+" if any? 105 | 106 | each do |flag| 107 | io << flag.flag 108 | end 109 | 110 | @parameterized_flags.each do |flag| 111 | io << " " 112 | io << flag.parameter 113 | end 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /irc/src/irc/network.cr: -------------------------------------------------------------------------------- 1 | module IRC 2 | class Network 3 | NONE = new 4 | 5 | property? account_notify 6 | property? extended_join 7 | 8 | def initialize 9 | @account_notify = false 10 | @extended_join = false 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /irc/src/irc/user.cr: -------------------------------------------------------------------------------- 1 | require "./modes" 2 | require "./membership" 3 | 4 | module IRC 5 | # USERMODE DESCRIPTION 6 | # +A - Is a Server Administrator 7 | # +a - Is a Services Administrator 8 | # +B - Marks you as being a Bot 9 | # +C - Is a Co Administrator 10 | # +D, +d - Deaf - ignores all channel messages. 11 | # +g - "caller id" mode only allow accept clients to message you (Freenode) 12 | # +g - Can read & send to GlobOps, and LocOps (UnrealIRCD) 13 | # +G - Filters out all Bad words in your messages with 14 | # +h - Available for Help (Help Operator) 15 | # +H - Hide IRCop status in /WHO and /WHOIS. (IRC Operators only) 16 | # +i - Designates this client 'invisible'. 17 | # +I - Hide an oper's idle time (in /whois output) from regular users. 18 | # +N - Is a Network Administrator 19 | # +o - Designates this client is an IRC Operator. 20 | # +O - Local IRC Operator 21 | # +p - Hide all channels in /whois and /who 22 | # +q - Only U:lines can kick you (Services Admins/Net Admins only) 23 | # +Q - Prevents you from being affected by channel forwarding. 24 | # +R - Allows you to only receive PRIVMSGs/NOTICEs from registered (+r) users 25 | # +r - Identifies the nick as being Registered (settable by services only) 26 | # +s - Can listen to Server notices 27 | # +S - For Services only. (Protects them) 28 | # +T - Prevents you from receiving CTCPs 29 | # +t - Says that you are using a /VHOST 30 | # +V - Marks the client as a WebTV user 31 | # +v - Receive infected DCC send rejection notices 32 | # +w - Can listen to Wallop messages 33 | # +W - Lets you see when people do a /WHOIS on you (IRC Operators only) 34 | # +x - Gives the user Hidden Hostname (security) 35 | # +Z, +z - Is connected via SSL (cannot be set or unset). 36 | class User 37 | property authname : String|Bool? 38 | property realname : String? 39 | getter channels 40 | getter mask 41 | getter modes 42 | delegate nick, user, host, to: mask 43 | 44 | def initialize(@mask : Mask) 45 | @authname = nil 46 | @realname = nil 47 | @channels = {} of String => Membership 48 | @modes = Modes.new 49 | end 50 | 51 | def name 52 | nick || user || @realname || @authname || host 53 | end 54 | 55 | def mode(flag, gained) 56 | if gained 57 | @modes.set flag 58 | else 59 | @modes.unset flag 60 | end 61 | end 62 | 63 | {% for item in [{['A'], "server_admin"}, {['a'], "service_admin"}, {['B'], "bot"}, 64 | {['C'], "co_admin"}, {['D', 'd'], "deaf"}, {['h'], "available_for_help"}, 65 | {['i'], "invisible"}, {['N'], "network_admin"}, {['o'], "operator"}, 66 | {['O'], "local_operator"}, {['r'], "registered"}, {['S'], "service"}, 67 | {['w'], "receive_wallops"}, {['z', 'Z'], "secure"},] %} 68 | {% flags = item[0] %} 69 | {% name = item[1] %} 70 | def {{name.id}}? 71 | {% for flag in flags %} 72 | @modes.set?({{flag}}) || 73 | {% end %} 74 | false # Cheat post fence problem 75 | end 76 | {% end %} 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /irc/src/irc/user_manager.cr: -------------------------------------------------------------------------------- 1 | require "./channel" 2 | require "./mask" 3 | require "./message" 4 | require "./membership" 5 | require "./modes" 6 | require "./user" 7 | 8 | module IRC 9 | class UserManager 10 | def initialize 11 | @users = {} of String => User 12 | end 13 | 14 | def register_handlers(connection) 15 | connection.on Message::RPL_NAMREPLY do |message| 16 | channel = connection.channels[message.parameters[2]] 17 | 18 | message.parameters.last.split(' ').each do |user| 19 | mode = user.chars.first 20 | user = user[1..-1] if {'@', '+'}.includes? mode 21 | 22 | membership = join Mask.parse(user), channel 23 | membership.modes.set 'o' if mode == '@' 24 | membership.modes.set 'v' if mode == '+' 25 | end 26 | end 27 | 28 | connection.on Message::JOIN do |message| 29 | channel = connection.channels[message.parameters.first] 30 | 31 | with_mask(message) do |mask| 32 | if connection.network.extended_join? 33 | authname = message.parameters[1] 34 | authname = false if authname == "*" 35 | realname = message.parameters.last 36 | user = find_user(mask) 37 | 38 | join mask, channel, authname 39 | user.realname = realname 40 | else 41 | join mask, channel 42 | end 43 | end 44 | end 45 | 46 | connection.on Message::PART do |message| 47 | with_mask(message) do |mask| 48 | channel = connection.channels[message.parameters.first] 49 | if mask.nick == connection.config.nick 50 | connection.part channel.name 51 | else 52 | part mask, channel 53 | end 54 | end 55 | end 56 | 57 | connection.on Message::QUIT do |message| 58 | with_mask(message) do |mask| 59 | quit mask 60 | end 61 | end 62 | 63 | connection.on Message::KICK do |message| 64 | channel, nick = message.parameters 65 | if nick == connection.config.nick 66 | connection.part channel 67 | else 68 | kick Mask.parse(nick), connection.channels[channel] 69 | end 70 | end 71 | 72 | connection.on Message::NICK do |message| 73 | with_mask(message) do |mask| 74 | nick = message.parameters.first 75 | nick mask, nick 76 | end 77 | end 78 | 79 | connection.on Message::ACCOUNT do |message| 80 | with_mask(message) do |mask| 81 | authname = message.parameters.first 82 | authname = false if authname == "*" 83 | 84 | auth_change mask, authname 85 | end 86 | end 87 | 88 | connection.on Message::RPL_WHOISUSER do |message| 89 | _me, nick, username, host, _unused, realname = message.parameters 90 | user = find_user Mask.parse(nick) 91 | 92 | user.mask.user = username 93 | user.mask.host = host 94 | user.realname = realname 95 | end 96 | 97 | connection.on Message::RPL_WHOISACCOUNT do |message| 98 | _me, nick, account = message.parameters 99 | user = find_user(Mask.parse(nick)) 100 | user.authname = account 101 | end 102 | 103 | connection.on Message::MODE do |message| 104 | target = message.parameters.first 105 | modes = message.parameters[1..-1].join(' ') 106 | 107 | if target.starts_with? "#" 108 | Modes::Parser.parse(modes) do |modifier, flag, parameter| 109 | if parameter && Channel::MEMBERHSIP_MODES.includes?(flag) 110 | mask = Mask.parse(parameter) 111 | parameter = nil unless {'b', 'e', 'I', 'q'}.includes?(flag) 112 | channel = connection.channels[target] 113 | 114 | cmode mask, channel, flag, (modifier == '+'), parameter 115 | end 116 | end 117 | else 118 | user = find_user Mask.parse(target) 119 | 120 | user.modes.parse modes 121 | end 122 | end 123 | end 124 | 125 | def with_mask(message) 126 | if prefix = message.prefix 127 | mask = Mask.parse prefix 128 | yield mask 129 | end 130 | end 131 | 132 | def find_user(mask : Mask) 133 | @users.fetch(mask.nick) { @users[mask.nick] = User.new(mask) }.tap &.mask.update(mask) 134 | end 135 | 136 | def find_membership(mask, channel : Channel) 137 | channels = find_user(mask).channels 138 | channels.fetch(channel.name) { channels[channel.name] = Membership.new(channel) } 139 | end 140 | 141 | def track(mask) 142 | find_user mask 143 | end 144 | 145 | def nick(mask, new_nick) 146 | find_user(mask).tap do |user| 147 | @users[new_nick] = @users.delete(mask.nick).not_nil! 148 | user.mask.nick = new_nick 149 | end 150 | end 151 | 152 | def join(mask, channel, authname=nil) 153 | find_user(mask).authname = authname if authname 154 | find_membership(mask, channel).tap &.join 155 | end 156 | 157 | def part(mask, channel) 158 | find_membership(mask, channel).tap &.part 159 | end 160 | 161 | def kick(mask, channel) 162 | part mask, channel 163 | end 164 | 165 | def quit(mask) 166 | @users.delete(mask.nick) 167 | end 168 | 169 | def auth_change(mask, authname) 170 | find_user(mask).tap &.authname=(authname) 171 | end 172 | 173 | def umode(mask, flag, gained) 174 | find_user(mask).tap &.mode(flag, gained) 175 | end 176 | 177 | def cmode(mask, channel, flag, gained, parameter=nil) 178 | find_membership(mask, channel).tap &.mode(flag, gained, parameter) 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /irc/src/irc/workers.cr: -------------------------------------------------------------------------------- 1 | require "./message" 2 | 3 | module IRC 4 | class Reader 5 | Log = IRC::Log.for("reader") 6 | 7 | def initialize(socket, channel, ping_channel) 8 | spawn do 9 | loop do 10 | break if channel.closed? 11 | begin 12 | line = socket.gets 13 | if line 14 | Log.debug { "r> #{line.chomp}" } 15 | message = Message.from(line) 16 | channel.send message if message 17 | else 18 | Log.fatal { "Socket closed, EOF!" } 19 | channel.send Message.from(":fake PING :fake").not_nil! 20 | channel.close 21 | socket.close unless socket.closed? 22 | break 23 | end 24 | rescue IO::TimeoutError | OpenSSL::SSL::Error 25 | if socket.closed? 26 | Log.fatal { "Socket closed while reading!" } 27 | channel.send Message.from(":fake PING :fake").not_nil! 28 | channel.close 29 | else 30 | Log.debug { "No message within 300 seconds, sending PING" } 31 | ping_channel.send "PING :debot" 32 | end 33 | rescue e : InvalidByteSequenceError 34 | Log.warn { "Failed to decode message: #{line.try &.bytes.inspect}" } 35 | rescue e : IO::Error 36 | unless e.os_error == Errno::EINTR 37 | Log.fatal { "Failed to read message: #{e.message} (#{e.class})" } 38 | else 39 | Log.debug { "Got #{e.class}: #{e.message}" } 40 | end 41 | rescue e 42 | Log.fatal { "Failed to read message: #{e.message} (#{e.class})" } 43 | end 44 | end 45 | Log.debug { "Stopped reader" } 46 | end 47 | end 48 | 49 | def stop 50 | # We'll just die as main exits 51 | end 52 | end 53 | 54 | class Sender 55 | Log = IRC::Log.for("sender") 56 | RATE_LIMIT = 3 # number of messages per second 57 | @last_write : Time 58 | 59 | @write_interval : Time::Span 60 | 61 | def initialize(socket, channel) 62 | @stop_signal = ::Channel(Symbol).new 63 | @last_write = 1.second.ago 64 | @write_interval = 1.fdiv(RATE_LIMIT).seconds 65 | spawn do 66 | begin 67 | loop do 68 | break if channel.closed? || @stop_signal.closed? 69 | message = ::Channel.receive_first(@stop_signal, channel) 70 | if message.is_a? String 71 | begin 72 | sleep @write_interval if Time.local - @last_write < @write_interval 73 | Log.debug { "w> #{message.chomp}" } 74 | message.to_s(socket) 75 | socket.flush 76 | @last_write = Time.local 77 | rescue IO::TimeoutError | OpenSSL::SSL::Error 78 | if socket.closed? 79 | Log.fatal { "Socket closed while writing!" } 80 | channel.close 81 | break 82 | end 83 | end 84 | elsif message == :stop 85 | Log.debug { "Sender received stop signal, shutting down" } 86 | channel.close 87 | socket.close unless socket.closed? 88 | break 89 | end 90 | end 91 | rescue e 92 | Log.fatal { "Failed to send message: #{e.message} (#{e.class})" } 93 | end 94 | 95 | Log.debug { "Stopped sender" } 96 | end 97 | end 98 | 99 | def stop 100 | Log.debug { "Stopping sender" } 101 | @stop_signal.send :stop 102 | @stop_signal.close 103 | end 104 | end 105 | 106 | class Processor 107 | Log = IRC::Log.for("processor") 108 | 109 | getter channel 110 | getter handlers 111 | 112 | def initialize 113 | @channel = ::Channel(Message).new(64) 114 | @handlers = Array(Message ->).new 115 | @pending_handlers = 0 116 | @stop_signal = ::Channel(Symbol).new 117 | 118 | process 119 | end 120 | 121 | def handle(&handler : Message ->) 122 | @handlers << handler 123 | handler 124 | end 125 | 126 | def on(*types, &handler : Message ->) 127 | handle do |message| 128 | handler.call(message) if types.includes? message.type 129 | end 130 | end 131 | 132 | def stop 133 | Log.debug { "Stopping processor" } 134 | @stop_signal.send :stop 135 | @stop_signal.close 136 | end 137 | 138 | def handle_others 139 | loop do 140 | pending_handlers = @pending_handlers 141 | Fiber.yield 142 | break if pending_handlers == @pending_handlers 143 | end 144 | end 145 | 146 | def process 147 | spawn do 148 | loop do 149 | break if @channel.closed? || @stop_signal.closed? 150 | message = ::Channel.receive_first(@stop_signal, @channel) 151 | if message.is_a? Message 152 | spawn_handlers message 153 | elsif message == :stop 154 | Log.debug { "Processor received stop signal, shutting down" } 155 | @channel.close 156 | break 157 | end 158 | end 159 | Log.debug { "Stopped processor" } 160 | exit 1 161 | end 162 | end 163 | 164 | private def spawn_handlers(message) 165 | @handlers.each do |handler| 166 | handle_others if @pending_handlers >= 100 167 | @pending_handlers += 1 168 | spawn do 169 | handler.call(message) 170 | @pending_handlers -= 1 171 | end 172 | end 173 | end 174 | end 175 | end 176 | --------------------------------------------------------------------------------