├── .gitignore ├── .jshintrc ├── .travis.yml ├── AUTHORS.rst ├── LICENSE ├── README.rst ├── bot.js ├── bot.json.dist ├── bot.py ├── docs ├── Makefile ├── conf.py ├── contributing.rst ├── faq.rst ├── future.rst ├── index.rst ├── install.rst ├── services.rst └── services_devel.rst ├── package.json ├── requirements.txt ├── services ├── __init__.py ├── admin.js ├── admin.json.dist ├── funday.py ├── github.js ├── github.json.dist ├── jira.json.dist ├── jira_feed.py ├── jira_ticket.js ├── lib │ ├── __init__.py │ ├── api.js │ ├── api.py │ └── colors.js ├── motivate.js ├── release.js ├── release.json.dist ├── semantics.js ├── troll.js ├── twsrs.py ├── twsrs_quotes.txt ├── weather.js ├── weather.json.dist ├── weblistener.js └── weblistener.json.dist └── tests ├── circus.ini ├── configs ├── bot.json ├── ngircd.conf └── redis.conf ├── test.sh ├── tests.js └── var ├── lib └── redis │ └── .git-folder ├── log └── .git-folder └── run └── .git-folder /.gitignore: -------------------------------------------------------------------------------- 1 | *config.js 2 | *config.py 3 | *.json 4 | node_modules 5 | *.pyc 6 | _build 7 | tests/var/lib 8 | tests/var/log 9 | tests/var/run -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "asi": true 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: 5 | - pip install flake8 --use-mirrors 6 | - sudo apt-get install nodejs 7 | - sudo npm install -g jshint 8 | script: 9 | - flake8 . 10 | - jshint *.js{,on.dist} services/*.js{,on.dist} 11 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Authors 2 | ~~~~~~~ 3 | 4 | Core Developers 5 | --------------- 6 | - Wraithan 7 | - Aaron Parecki 8 | 9 | Contributors 10 | ------------ 11 | https://github.com/zenirc/zenircbot/graphs/contributors 12 | 13 | Special Thanks 14 | -------------- 15 | Aquameta who sponsored and inspired initial development of this bot. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011 by Chris McDonald. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Repo 2 | **** 3 | The official repo is now https://github.com/zenirc/zenircbot 4 | 5 | Resources 6 | ********* 7 | 8 | * Docs on `Read The Docs`_ 9 | * Issues on `GitHub Issues`_ 10 | * Support in the `#pdxbots`_ channel on Freenode 11 | * Plans and updates on the bot on my blog_ under the `ZenIRCBot label`_. 12 | 13 | 14 | .. _`Read The Docs`: http://zenircbot.readthedocs.org/ 15 | .. _`GitHub Issues`: https://github.com/zenirc/zenircbot/issues?milestone=1&state=open 16 | .. _`#pdxbots`: irc://chat.freenode.net/#pdxbots 17 | .. _`blog`: http://blog.zenirc.net/ 18 | .. _`ZenIRCBot label`: http://blog.zenirc.net/search/label/zenircbot 19 | -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | var irc = require('irc'); 2 | var api = require('./services/lib/api'); 3 | var opts = require('nomnom') 4 | .option('config', { 5 | 'abbr': 'c', 6 | 'default': 'bot.json', 7 | 'help': 'config file to use' 8 | }).parse() 9 | 10 | zenircbot = { 11 | setup: function() { 12 | zenircbot.config = api.load_config(opts.config); 13 | zenircbot.pub = api.get_redis_client(zenircbot.config.redis); 14 | zenircbot.sub = api.get_redis_client(zenircbot.config.redis); 15 | 16 | zenircbot.unsetRedisKeys(); 17 | var cfg = zenircbot.server_config_for(0); 18 | console.log('irc server: '+cfg.hostname+' nick: '+cfg.nick); 19 | zenircbot.irc = new irc.Client(cfg.hostname, cfg.nick, cfg); 20 | var bot = zenircbot.irc 21 | 22 | bot.addListener('connect', function() { 23 | zenircbot.pub.set('zenircbot:nick', bot.nick); 24 | zenircbot.pub.set('zenircbot:admin_spew_channels', 25 | cfg.admin_spew_channels) 26 | }); 27 | 28 | bot.addListener('ctcp', function(nick, to, text, type) { 29 | console.log('action: ' + nick + ' said ' + text + ' to ' + to); 30 | if (to == bot.nick) { 31 | to = nick; 32 | } 33 | var msg = { 34 | version: 1, 35 | type: 'privmsg_action', 36 | data: { 37 | sender: nick, 38 | channel: to, 39 | message: text.replace(/^ACTION /, '') 40 | } 41 | }; 42 | zenircbot.pub.publish('in', JSON.stringify(msg)); 43 | }); 44 | 45 | bot.addListener('message', function(nick, to, text, message) { 46 | console.log(nick + ' said ' + text + ' to ' + to); 47 | if (to == bot.nick) { 48 | to = nick; 49 | } 50 | var msg = { 51 | version: 1, 52 | type: 'privmsg', 53 | data: { 54 | sender: nick, 55 | channel: to, 56 | message: text 57 | } 58 | }; 59 | zenircbot.pub.publish('in', JSON.stringify(msg)); 60 | }); 61 | 62 | bot.addListener('nick', function(oldNick, newNick) { 63 | zenircbot.pub.get('zenircbot:nick', function(err, nick) { 64 | if (nick == oldNick) { 65 | zenircbot.pub.set('zenircbot:nick', newNick); 66 | } 67 | }); 68 | }); 69 | 70 | bot.addListener('join', function(channel, nick, message) { 71 | console.log(nick + ' joined ' + channel); 72 | var msg = { 73 | version: 1, 74 | type: 'join', 75 | data: { 76 | sender: nick, 77 | channel: channel 78 | } 79 | }; 80 | zenircbot.pub.publish('in', JSON.stringify(msg)); 81 | }); 82 | 83 | bot.addListener('part', function(channel, nick, reason, message) { 84 | console.log(nick + ' left ' + channel + ' because ' + reason); 85 | var msg = { 86 | version: 1, 87 | type: 'part', 88 | data: { 89 | sender: nick, 90 | channel: channel 91 | } 92 | }; 93 | zenircbot.pub.publish('in', JSON.stringify(msg)); 94 | }); 95 | 96 | bot.addListener('quit', function(nick, reason, channels, message) { 97 | console.log(nick + ' quit'); 98 | var msg = { 99 | version: 1, 100 | type: 'quit', 101 | data: { 102 | sender: nick 103 | } 104 | }; 105 | zenircbot.pub.publish('in', JSON.stringify(msg)); 106 | }); 107 | 108 | bot.addListener('topic', function(channel, topic, nick, message) { 109 | console.log(nick + ' changed the topic in ' + channel + ' to "' + topic + '"'); 110 | var msg = { 111 | version: 1, 112 | type: 'topic', 113 | data: { 114 | sender: nick, 115 | channel: channel, 116 | topic: topic 117 | } 118 | }; 119 | zenircbot.pub.publish('in', JSON.stringify(msg)); 120 | }); 121 | 122 | bot.addListener('names', function(channel, nicks) { 123 | console.log('Names: '+channel); 124 | console.log(nicks); 125 | var msg = { 126 | version: 1, 127 | type: 'names', 128 | data: { 129 | channel: channel, 130 | nicks: nicks 131 | } 132 | }; 133 | zenircbot.pub.publish('in', JSON.stringify(msg)); 134 | }); 135 | 136 | bot.addListener('error', function(message) { 137 | console.log(message); 138 | }); 139 | 140 | var output_handlers = { 141 | 1: zenircbot.output_version_1 142 | }; 143 | 144 | zenircbot.sub.subscribe('out'); 145 | zenircbot.sub.on('message', function(channel, message) { 146 | var msg = JSON.parse(message); 147 | output_handlers[msg.version](bot, msg); 148 | }); 149 | }, 150 | unsetRedisKeys: function(){ 151 | zenircbot.pub.del('zenircbot:nick'); 152 | zenircbot.pub.del('zenircbot:admin_spew_channels'); 153 | }, 154 | server_config_for: function(idx) { 155 | var cfg = zenircbot.config.servers[idx]; 156 | /* server-generic options */ 157 | cfg.debug = zenircbot.config.options.debug; 158 | cfg.floodProtection = zenircbot.config.options.floodProtection; 159 | cfg.showErrors = zenircbot.config.options.debug; 160 | cfg.stripColors = zenircbot.config.options.stripColors; 161 | return cfg; 162 | }, 163 | output_version_1: function(bot, message) { 164 | switch (message.type) { 165 | case 'privmsg': 166 | console.log(' privmsg'); 167 | bot.say(message.data.to, message.data.message); 168 | break; 169 | case 'privmsg_action': 170 | console.log(' privmsg_action'); 171 | bot.say(message.data.to, '\u0001ACTION ' + message.data.message + 172 | '\u0001'); 173 | break; 174 | case 'raw': 175 | console.log(' raw'); 176 | bot.send(message.command); 177 | break; 178 | } 179 | } 180 | } 181 | 182 | zenircbot.setup(); 183 | -------------------------------------------------------------------------------- /bot.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "servers": [ 3 | { 4 | "name": "freenode", 5 | "hostname": "irc.freenode.net", 6 | "nick": "ZenIRCBot", 7 | "userName": "zenircbot", 8 | "realName": "ZenIRCBot", 9 | "port": 6667, 10 | "autoRejoin": true, 11 | "autoConnect": true, 12 | "channels": ["#pdxbots"], 13 | "admin_nicks": "YourNick", 14 | "admin_spew_channels": ["#pdxbots"], 15 | "secure": false, 16 | "selfSigned": false 17 | } 18 | ], 19 | "options": { 20 | "debug": false, 21 | "floodProtection": true, 22 | "stripColors": false 23 | }, 24 | "redis": { 25 | "host": "localhost", 26 | "port": 6379, 27 | "db": 0 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import gevent 2 | import json 3 | import argparse 4 | 5 | from irc import IRCBot, run_bot 6 | from gevent import monkey 7 | from services.lib.api import load_config, get_redis_client 8 | 9 | 10 | monkey.patch_all() 11 | 12 | parser = argparse.ArgumentParser( 13 | description='ZenIRCBot, a new and different bot', 14 | ) 15 | parser.add_argument('-c', '--config', default='./bot.json', 16 | help='The config to use. (default: %(default)s)') 17 | opts = parser.parse_args() 18 | 19 | config = load_config(opts.config) 20 | pub = get_redis_client(config['redis']) 21 | 22 | 23 | class RelayBot(IRCBot): 24 | 25 | def __init__(self, *args, **kwargs): 26 | super(RelayBot, self).__init__(*args, **kwargs) 27 | pub.set('zenircbot:nick', self.conn.nick) 28 | gevent.spawn(self.do_sub) 29 | 30 | def do_sub(self): 31 | sub = get_redis_client(config['redis']) 32 | self.pubsub = sub.pubsub() 33 | self.pubsub.subscribe('out') 34 | for msg in self.pubsub.listen(): 35 | if msg['type'] != 'message': 36 | continue 37 | message = json.loads(msg['data']) 38 | print 'Got %s' % message 39 | if message['version'] == 1: 40 | if message['type'] == 'privmsg': 41 | self.respond(message['data']['message'], 42 | channel=message['data']['to']) 43 | 44 | def do_privmsg(self, nick, message, channel): 45 | if not channel: 46 | channel = nick 47 | to_publish = json.dumps({ 48 | 'version': 1, 49 | 'type': 'privmsg', 50 | 'data': { 51 | 'sender': nick, 52 | 'channel': channel, 53 | 'message': message, 54 | }, 55 | }) 56 | 57 | pub.publish('in', to_publish) 58 | print 'Sending to in %s' % to_publish 59 | 60 | def do_part(self, nick, command, channel): 61 | to_publish = json.dumps({ 62 | 'version': 1, 63 | 'type': 'part', 64 | 'data': { 65 | 'sender': nick, 66 | 'channel': channel, 67 | } 68 | }) 69 | pub.publish('in', to_publish) 70 | print 'Sending to in %s' % to_publish 71 | 72 | def do_quit(self, command, nick, channel): 73 | to_publish = json.dumps({ 74 | 'version': 1, 75 | 'type': 'quit', 76 | 'data': { 77 | 'sender': nick, 78 | } 79 | }) 80 | pub.publish('in', to_publish) 81 | print 'Sending to in %s' % to_publish 82 | 83 | def do_nick(self, old_nick, command, new_nick): 84 | if pub.get('zenircbot:nick') == old_nick: 85 | pub.set('zenircbot:nick', new_nick) 86 | print 'nick change: %s -> %s' % (old_nick, new_nick) 87 | 88 | def command_patterns(self): 89 | return ( 90 | ('/privmsg', self.do_privmsg), 91 | ('/part', self.do_part), 92 | ('/quit', self.do_quit), 93 | ('/nick', self.do_nick), 94 | ) 95 | 96 | 97 | run_bot(RelayBot, config['servers'][0]['hostname'], 98 | config['servers'][0]['port'], config['servers'][0]['nick'], 99 | config['servers'][0]['channels']) 100 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ZenIRCBot.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ZenIRCBot.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/ZenIRCBot" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ZenIRCBot" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # ZenIRCBot documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Sep 21 12:38:37 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing 7 | # dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | #import sys, os 16 | 17 | # If extensions (or modules to document with autodoc) are in another directory, 18 | # add these directories to sys.path here. If the directory is relative to the 19 | # documentation root, use os.path.abspath to make it absolute, like shown here. 20 | #sys.path.insert(0, os.path.abspath('.')) 21 | 22 | # -- General configuration ---------------------------------------------------- 23 | 24 | # If your documentation needs a minimal Sphinx version, state it here. 25 | #needs_sphinx = '1.0' 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 29 | extensions = [] 30 | 31 | # Add any paths that contain templates here, relative to this directory. 32 | templates_path = ['_templates'] 33 | 34 | # The suffix of source filenames. 35 | source_suffix = '.rst' 36 | 37 | # The encoding of source files. 38 | #source_encoding = 'utf-8-sig' 39 | 40 | # The master toctree document. 41 | master_doc = 'index' 42 | 43 | # General information about the project. 44 | project = u'ZenIRCBot' 45 | copyright = u'2011, Wraithan' 46 | 47 | # The version info for the project you're documenting, acts as replacement for 48 | # |version| and |release|, also used in various other places throughout the 49 | # built documents. 50 | # 51 | # The short X.Y version. 52 | version = '2.2' 53 | # The full version, including alpha/beta/rc tags. 54 | release = '2.2.1' 55 | 56 | # The language for content autogenerated by Sphinx. Refer to documentation 57 | # for a list of supported languages. 58 | #language = None 59 | 60 | # There are two options for replacing |today|: either, you set today to some 61 | # non-false value, then it is used: 62 | #today = '' 63 | # Else, today_fmt is used as the format for a strftime call. 64 | #today_fmt = '%B %d, %Y' 65 | 66 | # List of patterns, relative to source directory, that match files and 67 | # directories to ignore when looking for source files. 68 | exclude_patterns = ['_build'] 69 | 70 | # The reST default role (used for this markup: `text`) to use for all 71 | # documents. 72 | #default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | #add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | #add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | #show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = 'sphinx' 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | #modindex_common_prefix = [] 90 | 91 | 92 | # -- Options for HTML output -------------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. See the documentation for 95 | # a list of builtin themes. 96 | html_theme = 'default' 97 | 98 | # Theme options are theme-specific and customize the look and feel of a theme 99 | # further. For a list of options available for each theme, see the 100 | # documentation. 101 | #html_theme_options = {} 102 | 103 | # Add any paths that contain custom themes here, relative to this directory. 104 | #html_theme_path = [] 105 | 106 | # The name for this set of Sphinx documents. If None, it defaults to 107 | # " v documentation". 108 | #html_title = None 109 | 110 | # A shorter title for the navigation bar. Default is the same as html_title. 111 | #html_short_title = None 112 | 113 | # The name of an image file (relative to this directory) to place at the top 114 | # of the sidebar. 115 | #html_logo = None 116 | 117 | # The name of an image file (within the static path) to use as favicon of the 118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 119 | # pixels large. 120 | #html_favicon = None 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['_static'] 126 | 127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 128 | # using the given strftime format. 129 | #html_last_updated_fmt = '%b %d, %Y' 130 | 131 | # If true, SmartyPants will be used to convert quotes and dashes to 132 | # typographically correct entities. 133 | #html_use_smartypants = True 134 | 135 | # Custom sidebar templates, maps document names to template names. 136 | #html_sidebars = {} 137 | 138 | # Additional templates that should be rendered to pages, maps page names to 139 | # template names. 140 | #html_additional_pages = {} 141 | 142 | # If false, no module index is generated. 143 | #html_domain_indices = True 144 | 145 | # If false, no index is generated. 146 | #html_use_index = True 147 | 148 | # If true, the index is split into individual pages for each letter. 149 | #html_split_index = False 150 | 151 | # If true, links to the reST sources are added to the pages. 152 | #html_show_sourcelink = True 153 | 154 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 155 | #html_show_sphinx = True 156 | 157 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 158 | #html_show_copyright = True 159 | 160 | # If true, an OpenSearch description file will be output, and all pages will 161 | # contain a tag referring to it. The value of this option must be the 162 | # base URL from which the finished HTML is served. 163 | #html_use_opensearch = '' 164 | 165 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 166 | #html_file_suffix = None 167 | 168 | # Output file base name for HTML help builder. 169 | htmlhelp_basename = 'ZenIRCBotdoc' 170 | 171 | 172 | # -- Options for LaTeX output ------------------------------------------------- 173 | 174 | # The paper size ('letter' or 'a4'). 175 | #latex_paper_size = 'letter' 176 | 177 | # The font size ('10pt', '11pt' or '12pt'). 178 | #latex_font_size = '10pt' 179 | 180 | # Grouping the document tree into LaTeX files. List of tuples 181 | # (source start file, target name, title, author, documentclass 182 | # [howto/manual]). 183 | latex_documents = [ 184 | ('index', 'ZenIRCBot.tex', u'ZenIRCBot Documentation', 185 | u'Wraithan', 'manual'), 186 | ] 187 | 188 | # The name of an image file (relative to this directory) to place at the top of 189 | # the title page. 190 | #latex_logo = None 191 | 192 | # For "manual" documents, if this is true, then toplevel headings are parts, 193 | # not chapters. 194 | #latex_use_parts = False 195 | 196 | # If true, show page references after internal links. 197 | #latex_show_pagerefs = False 198 | 199 | # If true, show URL addresses after external links. 200 | #latex_show_urls = False 201 | 202 | # Additional stuff for the LaTeX preamble. 203 | #latex_preamble = '' 204 | 205 | # Documents to append as an appendix to all manuals. 206 | #latex_appendices = [] 207 | 208 | # If false, no module index is generated. 209 | #latex_domain_indices = True 210 | 211 | 212 | # -- Options for manual page output ------------------------------------------- 213 | 214 | # One entry per manual page. List of tuples 215 | # (source start file, name, description, authors, manual section). 216 | man_pages = [ 217 | ('index', 'zenircbot', u'ZenIRCBot Documentation', 218 | [u'Wraithan'], 1) 219 | ] 220 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | License 5 | ------- 6 | 7 | ZenIRCBot is covered by an MIT license, any code contributed will be 8 | required to be covered by this license as well. 9 | 10 | Issues 11 | ------ 12 | 13 | The `GitHub issue tracker`_ is the prefered method of reporting an 14 | issue. Please do not apply any tags or milestones, I will do that 15 | myself when I triage the issue. 16 | 17 | .. _`GitHub issue tracker`: https://github.com/zenirc/zenircbot/issues 18 | 19 | Code 20 | ---- 21 | 22 | If you write code and would like it integrated with the code base 23 | please follow the following steps: 24 | 25 | #. Fork the repo_ 26 | #. Create a branch in your repo_ 27 | #. Do you bug fix/feature add/hackery 28 | #. Send a pull request from that branch 29 | #. Do not delete your fork until the pull request has been accepted or 30 | declined in such a way that you do not want to continue development 31 | on it. 32 | 33 | .. _repo: https://github.com/zenirc/zenircbot 34 | 35 | Tests 36 | ----- 37 | 38 | There are some integration tests in the `tests folder`_. You will need 39 | to have circus_, and ngIRCd_ installed in order to use them. You run 40 | them like so:: 41 | 42 | $ cd tests/ 43 | $ ./test.sh 44 | 45 | This will bootstrap an environment that includes an IRC daemon and 46 | redis running on non standard ports. Then it fires up the bot then 47 | runs the tests. So far this works on my systems, please open an issue 48 | if you find that it doesn't work for you. 49 | 50 | Do not feel obligated to add tests for services, only if you are 51 | expanding the protocol that the bot itself is away of. 52 | 53 | .. _`tests folder`: https://github.com/zenirc/zenircbot/tree/master/tests 54 | .. _circus: https://github.com/mozilla-services/circus 55 | .. _ngIRCd: http://ngircd.barton.de/ 56 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | Frequently Asked Questions 2 | ========================== 3 | 4 | Can I run 2 bots in one Redis instance? 5 | --------------------------------------- 6 | 7 | No, even if you specify different database numbers. The issue is that 8 | the pub/sub parts of Redis are shared between all databases in an 9 | instance. Instead you'll have to run a Redis instance and set of 10 | services per bot. 11 | -------------------------------------------------------------------------------- /docs/future.rst: -------------------------------------------------------------------------------- 1 | Future of ZenIRCBot 2 | =================== 3 | 4 | Deprecation 5 | ----------- 6 | Deprecation protocol is as such: A feature is marked as deprecated. It 7 | then has warnings attached. In the next version the feature has louder 8 | warnings about using it. The version after that it is removed and no 9 | longer available. 10 | 11 | 12 | 2.2 13 | ~~~ 14 | * Use of services/lib/api.* is now deprecated 15 | Please instead install one of the libraries available as part of zenircbot-api 16 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ZenIRCBot 2 | ===================================== 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | install 10 | services 11 | services_devel 12 | faq 13 | future 14 | contributing 15 | 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | 24 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Install 2 | ======= 3 | 4 | Installing ZenIRCBot will one day be much simpler, but for now there 5 | are a number of steps to it. 6 | 7 | To start with, the bot is written in both JavaScript_ using the Node_ 8 | platform, and there is a version that is written in Python_. Two of 9 | the services are written in or rely on Python_ the rest are written in 10 | JavaScript. 11 | 12 | If you are on Ubuntu, then the quickest and easiest way to get a recent 13 | version of node is to use a version from a PPA_ (Personal Package Archive). 14 | This PPA containes the latest stable versions of nodejs. 15 | 16 | To install from the PPA make sure you have ``python-software-properties`` 17 | installed first; otherwise, you might be missing the ``add-apt-repository`` 18 | command. (Install ``software-properties-common`` on Quantal). 19 | 20 | Then run:: 21 | 22 | $ sudo add-apt-repository ppa:chris-lea/node.js 23 | $ sudo apt-get update 24 | $ sudo apt-get install nodejs npm 25 | 26 | If you don't want to use the PPA_ then you can always compile node_ and 27 | npm_ yourself. 28 | 29 | 30 | Configuring the bot 31 | ------------------- 32 | 33 | The config for the bot can be found in `bot.json` which is a JSON file 34 | that both the Node.js and Python bots use. 35 | 36 | If you don't have a `bot.json`, copy `bot.json.dist` into its place 37 | like so:: 38 | 39 | $ cp bot.json{.dist,} 40 | 41 | Then modify it and fill in the values with your own. 42 | 43 | .. note:: Despite the option being `servers` ZenIRCBot currently only 44 | supports 1 server. It is named as such for future 45 | compatibility 46 | 47 | Getting the node.js bot running 48 | ------------------------------- 49 | 50 | To start with you'll need to install Node_, npm_ and Redis_. Once you 51 | have those you'll need to use npm to install the dependencies:: 52 | 53 | $ npm install 54 | 55 | Then all you need to do is start the bot:: 56 | 57 | $ node bot.js 58 | 59 | Voilà, your bot should connect, join the channels in the config and go 60 | forth on its merry way. It wont do anything interesting until you 61 | start up services. You can find information on starting up :doc:`services`. 62 | 63 | Getting the python bot running 64 | ------------------------------ 65 | 66 | .. warning:: 67 | 68 | This version of the bot is less battle tested than the node 69 | version. This doesn't mean you shouldn't use it, just know that 70 | these instructions may change in the near future. Also it doesn't 71 | use all of the options in the ``bot.json`` 72 | 73 | To start with you'll need to install Python_, virtualenv_, libevent 74 | (libevent-dev if you are on ubuntu) and Redis_ (all three provided by 75 | your OS package manager). Once you have those you'll want to make and 76 | activate the virtualenv to keep your libraries you installed for 77 | ZenIRCBot (if you have virtualenvwrapper installed already feel free 78 | to use it of course):: 79 | 80 | $ virtualenv zib 81 | $ source zib/bin/activate 82 | 83 | Then use pip to install the dependencies:: 84 | 85 | $ pip install -r requirements.txt 86 | 87 | Then all you need to do is run:: 88 | 89 | $ python bot.py 90 | 91 | Voilà, your bot should connect, and go forth on its merry way. It wont 92 | do anything interesting until you start up services. You can find 93 | information on starting up :doc:`services`. 94 | 95 | Getting the clojure bot running 96 | ------------------------------- 97 | 98 | .. warning:: 99 | 100 | This version of the bot is less battle tested than the node 101 | version. This doesn't mean you shouldn't use it, just know that 102 | these instructions may change in the near future. Also it doesn't 103 | use all of the options in the ``bot.json`` 104 | 105 | To start with you'll need to install Clojure_, Leiningen_ and 106 | Redis_. Once you have those installed you'll check out the clojure bot 107 | with:: 108 | 109 | $ git submodule init 110 | $ git submodule update 111 | 112 | Then you'll start the bot with:: 113 | 114 | $ cd clojurebot 115 | $ lein trampoline run -m zenircbot-clojure.core 116 | 117 | Voilà, your bot should connect, join the channels in the config and go 118 | forth on its merry way. It wont do anything interesting until you 119 | start up services. You can find information on starting up :doc:`services`. 120 | 121 | 122 | .. _JavaScript: http://en.wikipedia.org/wiki/JavaScript 123 | .. _node: http://nodejs.org 124 | .. _Python: http://python.org 125 | .. _npm: http://npmjs.org 126 | .. _Redis: http://redis.io 127 | .. _node-irc: https://github.com/martynsmith/node-irc 128 | .. _virtualenv: http://pypi.python.org/pypi/virtualenv 129 | .. _irckit: https://github.com/coleifer/irc 130 | .. _Clojure: http://clojure.org/ 131 | .. _Leiningen: https://github.com/technomancy/leiningen 132 | .. _PPA: https://launchpad.net/~chris-lea/+archive/node.js/ 133 | -------------------------------------------------------------------------------- /docs/services.rst: -------------------------------------------------------------------------------- 1 | Services 2 | ======== 3 | 4 | This is the documentation for the individual services that come with 5 | ZenIRCBot. For all of the `Node.js`_ based services you'll need to 6 | have the node `redis`_ library installed. For the `Python`_ based 7 | services you'll need to need to have the python `redis`_ library 8 | installed:: 9 | 10 | $ npm install redis # for Node 11 | 12 | $ pip install redis # for Python 13 | 14 | 15 | .. _admin: 16 | 17 | Admin 18 | ----- 19 | 20 | This is a service for doing basic things like starting and stopping other 21 | services or restarting the bot. You'll need to also have semantics_ running as 22 | it provides the ``directed_privmsg`` type that `admin.js` relies on. It is 23 | written in `Node.js`_ and also relies on `forever`_:: 24 | 25 | $ npm install forever 26 | 27 | Config 28 | ~~~~~~ 29 | 30 | The config is very simple, a single option called ``services`` which 31 | is a list of services you want started when you start `admin.js` 32 | 33 | Commands 34 | ~~~~~~~~ 35 | start 36 | This will start the specified service. 37 | 38 | restart 39 | This will restart the specific service. 40 | 41 | stop 42 | This will restart the specific service. 43 | 44 | pull 45 | This will pull down new code. 46 | 47 | .. _github: 48 | 49 | Github 50 | ------ 51 | 52 | This service relies on the weblistener_ service to pass along the post 53 | body with the service set in the JSON envelope. It is written in `Node.js`_ 54 | 55 | .. _jira_feed: 56 | 57 | Jira Feed 58 | --------- 59 | 60 | This service does a check on the specified JIRA issue RSS feed and 61 | posts to the channel whenever an issue is created, closed, or 62 | reopened. It is written in `Python`_. 63 | 64 | .. _jira_ticket: 65 | 66 | Jira Ticket 67 | ----------- 68 | 69 | This service watches for something formated with 2 letters, a dash, 70 | then numbers. For example BH-1234, it takes that, and appends it to 71 | the specified JIRA URL and says it back to the channel so you get 72 | links to issues automatically. It is written in `Node.js`_. 73 | 74 | .. _release: 75 | 76 | Release 77 | ------- 78 | 79 | This service is akin to the github_ service in that it relies on the 80 | weblistener_ service to send it data when something is posted to the 81 | port on the machine running the weblistener_ service. The post body 82 | should look like:: 83 | 84 | 85 | payload: "{ 86 | branch: 'feature/cool_stuff', 87 | status: 'started', 88 | hostname: 'staging-04', 89 | }" 90 | 91 | Where payload is the post variable, and what it contains is a JSON 92 | string with the attributes branch, status, and hostname. And it will 93 | then emit something like:: 94 | 95 | release of feature/cool_stuff started on staging-04 96 | 97 | To the channel specified in the config. It is written in `Node.js`_. 98 | 99 | .. _semantics: 100 | 101 | Semantics 102 | --------- 103 | 104 | This service adds another message type that is sent out over `in` 105 | which is the `directed_privmsg` type. These are messages that have 106 | been determined to have been sent to the bot via the following two 107 | forms:: 108 | 109 | ZenIRCBot: commands 110 | !commands 111 | 112 | It will also grab messages that were sent directly to the bot via a 113 | PM. It sends the standard envelope like the `privmsg` type. Its `data` 114 | attribute is slightly different though:: 115 | 116 | "data": { 117 | "raw_message": "!commands", 118 | "message": "commands", 119 | "sender": "Wraithan", 120 | "channel": "#pdxbots" 121 | } 122 | 123 | It strips the the way it was determined to be addressed to the bot so 124 | you can listen specifically for commands to come through rather than 125 | having to check all possible methods for a person to send a direct 126 | message. It is written in `Node.js`_. 127 | 128 | .. _troll: 129 | 130 | Troll 131 | ----- 132 | 133 | This service is a basic trolling service. It is written in `Node.js`_. 134 | 135 | Commands 136 | ~~~~~~~~ 137 | 138 | ls 139 | Responds with a funny picture. 140 | irssi 141 | Responds with a suggestion to use weechat. 142 | 143 | .. _twsrs: 144 | 145 | TWSRS 146 | ----- 147 | That's What She Really Said 148 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 149 | 150 | This is a service inspired by `talkbackbot`_ and steals its quote DB 151 | which it got a lot of from this `quote source`_. 152 | 153 | Commands 154 | ~~~~~~~~ 155 | 156 | That's what she said 157 | Responds with a quote from a famous woman when this is said in a 158 | channel. 159 | twsrs 160 | This is an actual command and allows one to get quotes without 161 | having to say that's what she said. 162 | 163 | .. _weblistener: 164 | 165 | Weblistener 166 | ----------- 167 | 168 | This is a service that passes along post data to the `web_in` channel in 169 | redis in the format of:: 170 | 171 | body: { 172 | payload: "JSON String", 173 | app: 'whatever-path-on-the-url-posted-to', 174 | } 175 | 176 | Where payload is the POST body and app is 177 | http://example.com/whatever-path-on-the-url-posted-to for example. It 178 | is written in `Node.js`_ and also relies on having `express`_ 179 | installed:: 180 | 181 | $ npm install express 182 | 183 | .. _`Node.js`: http://nodejs.com/ 184 | .. _`Python`: http://python.org/ 185 | .. _`redis`: http://redis.io/ 186 | .. _`forever`: https://github.com/nodejitsu/forever 187 | .. _`express`: http://expressjs.com/ 188 | .. _`talkbackbot`: https://github.com/jessamynsmith/talkbackbot 189 | .. _`quote source`: http://womenshistory.about.com/library/qu/blqulist.htm 190 | -------------------------------------------------------------------------------- /docs/services_devel.rst: -------------------------------------------------------------------------------- 1 | Developing Services 2 | =================== 3 | 4 | Service Model 5 | ------------- 6 | 7 | ZenIRCBot is a bit unique in how it works. A lot of IRC bots have 8 | plugins and you put the in the plugin directory, they get loaded when 9 | the bot starts. This model has worked pretty well in past for bots, 10 | the down side being you have to either have a really complex system to 11 | be able to dynamically load and unload plugins on the fly, or restart 12 | the bot in order to load new plugins. Also the plugins had to be 13 | written in the same language as the bot or one of the explicitly 14 | supported languages. 15 | 16 | This bot on the other hand has no plugins, and adding or removing 17 | functionality has nothing to do with core bot itself. Not just that, 18 | but what you can do with the plugins is only limited by your 19 | imagination as they are their own processes. 20 | 21 | When you start bot.js it connects to redis and subscribes to a pub/sub 22 | channel called 'out'. Any messages that come from IRC are published to 23 | 'in'. Then when you start a service, if it needs to know/process IRC 24 | messages it subscribes to the 'in' channel. If it wants to interact 25 | with the bot in some fashion, it publishes to the 'out' channel. 26 | Decoupling in this way allows for services to be written in any 27 | language, be running constantly or just as needed. 28 | 29 | Writing Your Own 30 | ---------------- 31 | 32 | When writing your own service, the easiest way to do so is to place it 33 | in the ``services`` directory. This way you can use admin.js_ to 34 | start, restart, and stop your service. Also you get access to 35 | ``lib/api`` which currently has nodejs and python versions. The things 36 | in ``lib/api`` are mostly convenience functions. The details of what 37 | is in there will be discussed later. 38 | 39 | First lets look at the low level protocol. Messages all have the same 40 | envelope:: 41 | 42 | { 43 | "version": 1, 44 | "type": "", 45 | "data": { 46 | ... 47 | } 48 | } 49 | 50 | In messages 51 | ~~~~~~~~~~~ 52 | 53 | The possible ``in`` messages. 54 | 55 | .. js:data:: "privmsg" 56 | 57 | Sent whenever a privmsg comes in:: 58 | 59 | { 60 | "version": 1, 61 | "type": "privmsg", 62 | "data": { 63 | "sender": "", 64 | "channel": "", 65 | "message": "" 66 | } 67 | } 68 | 69 | .. js:data:: "privmsg_action" 70 | 71 | Sent whenever an "ACTION" comes in:: 72 | 73 | { 74 | "version": 1, 75 | "type": "privmsg_action", 76 | "data": { 77 | "sender": "", 78 | "channel": "", 79 | "message": "" 80 | } 81 | } 82 | 83 | The ``message`` property contains the text portion of what was 84 | sent, the IRC-level "\u0001ACTION" prefix is already removed. 85 | 86 | .. js:data:: "join" 87 | 88 | Sent whenever someone joins a channel:: 89 | 90 | { 91 | "version": 1, 92 | "type": "join", 93 | "data": { 94 | "sender": "", 95 | "channel": "" 96 | } 97 | } 98 | 99 | .. js:data:: "part" 100 | 101 | Sent whenever someone leaves a channel:: 102 | 103 | { 104 | "version": 1, 105 | "type": "part", 106 | "data": { 107 | "sender": "", 108 | "channel": "" 109 | } 110 | } 111 | 112 | .. js:data:: "quit" 113 | 114 | Sent whenever someone quits:: 115 | 116 | { 117 | "version": 1, 118 | "type": "quit", 119 | "data": { 120 | "sender": "" 121 | } 122 | } 123 | 124 | .. js:data:: "topic" 125 | 126 | Sent whenever a channel's topic is changed:: 127 | 128 | { 129 | "version": 1, 130 | "type": "topic", 131 | "data": { 132 | "sender": "", 133 | "channel": "", 134 | "topic": "" 135 | } 136 | } 137 | 138 | .. js:data:: "names" 139 | 140 | Sent whenever a list of names is received for a channel. 141 | 142 | If you had nicks: Wraithan, zenircbot, and @aaronpk in #pdxbots:: 143 | 144 | { 145 | "version": 1, 146 | "type": "names", 147 | "data": { 148 | "channel": "#pdxbots", 149 | "nicks": { 150 | "Wraithan": "", 151 | "zenircbot": "", 152 | "aaronpk": "@" 153 | } 154 | } 155 | } 156 | 157 | This can be triggered by sending a raw command of "NAMES #channel" 158 | 159 | Out messages 160 | ~~~~~~~~~~~~ 161 | 162 | The possible ``out`` messages. 163 | 164 | .. js:data:: "privmsg" 165 | 166 | Used to have the bot say something:: 167 | 168 | { 169 | "version": 1, 170 | "type": "privmsg", 171 | "data": { 172 | "to": "", 173 | "message": "" 174 | } 175 | } 176 | 177 | 178 | .. js:data:: "privmsg_action" 179 | 180 | Used to have the bot send an action:: 181 | 182 | { 183 | "version": 1, 184 | "type": "privmsg_action", 185 | "data": { 186 | "to": "", 187 | "message": "" 188 | } 189 | } 190 | 191 | 192 | .. js:data:: "raw" 193 | 194 | Used to have the bot send a raw string to the IRC server:: 195 | 196 | { 197 | "version": 1, 198 | "type": "raw", 199 | "data": { 200 | "command": "" 201 | } 202 | } 203 | 204 | 205 | API Library 206 | ----------- 207 | 208 | .. warning:: 209 | 210 | The following API is depreciated, use the `new API libraries`_ instead. 211 | 212 | .. _`new API libraries`: http://zenircbot-api.rtfd.org/ 213 | 214 | These are the functions that can be found in the python and node.js 215 | api library. 216 | 217 | .. js:function:: send_privmsg(channel, message) 218 | 219 | :param string channel: The channel to send the message to. 220 | :param string message: The message to send. 221 | 222 | This is a helper so you don't have to handle the JSON or the 223 | envelope yourself. 224 | 225 | .. js:function:: send_admin_message(message) 226 | 227 | :param string message: The message to send. 228 | 229 | This is a helper function that sends the message to all of the 230 | channels defined in ``admin_spew_channels``. 231 | 232 | .. js:function:: register_commands(script, commands) 233 | 234 | :param string script: The script with extension that you are registering. 235 | :param list commands: A list of objects with name and description 236 | attributes used to reply to a commands query. 237 | 238 | This will notify all ``admin_spew_channels`` of the script coming 239 | online when the script registers itself. It will also setup a 240 | subscription to the 'out' channel that listens for 'commands' to be 241 | sent to the bot and responds with the list of script, command name, 242 | and command description for all registered scripts. 243 | 244 | .. js:function:: load_config(name) 245 | 246 | :param string name: The JSON file to load. 247 | :returns: An native object with the contents of the JSON file. 248 | 249 | This is a helper so you don't have to do the file IO and JSON 250 | parsing yourself. 251 | 252 | .. note:: 253 | If you port ``zenircbot-api`` to another language, please send a 254 | pull request with it, I'll gladly add it and maintain it to stay 255 | up to date with any protocol changes. 256 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Wraithan (http://wraithan.net)", 3 | "name": "zenircbot", 4 | "description": "ZenIRCBot", 5 | "version": "2.2.1", 6 | "homepage": "http://github.com/zenirc/zenircbot", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/zenirc/zenircbot.git" 10 | }, 11 | "dependencies": {"irc": "*", 12 | "redis": "*", 13 | "forever": "*", 14 | "nomnom": "*", 15 | "zenircbot-api": "~2"}, 16 | "devDependencies": {"nodeunit": "*"}, 17 | "engines": { 18 | "node": "*" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gevent 2 | -e git+git://github.com/coleifer/irc.git#egg=irckit 3 | redis 4 | argparse 5 | zenircbot_api 6 | -------------------------------------------------------------------------------- /services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenirc/zenircbot/bd9974f6dc27716a6aebd51cac882fc3a326b7b6/services/__init__.py -------------------------------------------------------------------------------- /services/admin.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | var api = require('zenircbot-api') 3 | var admin_config = api.load_config('./admin.json'); 4 | var bot_config = api.load_config('../bot.json'); 5 | var zen = new api.ZenIRCBot(bot_config.redis.host, 6 | bot_config.redis.port, 7 | bot_config.redis.db); 8 | var sub = zen.get_redis_client() 9 | var service_regex = /(\w+) (.*)/; 10 | var forever = require('forever'); 11 | var services = {}; 12 | 13 | function puts(error, stdout, stderr) { console.log(stdout); } 14 | 15 | language_map = {'js': 'node', 16 | 'py': 'python', 17 | 'rb': 'ruby'} 18 | 19 | admin_config.services.forEach(start_service) 20 | 21 | zen.register_commands("admin.js", [ 22 | {name: "start ", 23 | description: "This will start the service mentioned."}, 24 | {name: "restart ", 25 | description: "This will restart the service mentioned if it was started via admin.js."}, 26 | {name: "stop ", 27 | description: "This will stop the service mentioned if it was started via admin.js."}, 28 | {name: "pull", 29 | description: "This will pull down the code for the zenircbot."}]); 30 | 31 | sub.subscribe('in'); 32 | sub.on('message', function(channel, message){ 33 | var msg = JSON.parse(message); 34 | if (msg.version == 1 && msg.type == 'directed_privmsg') { 35 | if (bot_config.servers[0].admin_nicks.indexOf(msg.data.sender) != -1) { 36 | if (service_regex.test(msg.data.message)) { 37 | var result = service_regex.exec(msg.data.message); 38 | if (result[1] == 'start') { 39 | start_service(result[2]); 40 | } else if (result[1] == 'restart') { 41 | restart_service(result[2]); 42 | } else if (result[1] == 'stop') { 43 | stop_service(result[2]); 44 | } 45 | } else if (msg.data.message == 'pull') { 46 | git_pull(); 47 | } 48 | } 49 | } 50 | }); 51 | 52 | function start_service(service) { 53 | if (services[service]) { 54 | if (services[service].running) { 55 | zen.send_admin_message(service + ' is already running'); 56 | } else { 57 | zen.send_admin_message('starting ' + service); 58 | services[service].start(); 59 | } 60 | } else { 61 | zen.send_admin_message('starting ' + service); 62 | child = forever.start([language_map[service.split('.')[1]], service], { 63 | max: 10000, 64 | silent: false 65 | }); 66 | forever.startServer(child); 67 | services[service] = child; 68 | forever.cli.list(); 69 | } 70 | } 71 | 72 | function restart_service(service) { 73 | if (!services[service] || !services[service].running) { 74 | zen.send_admin_message(service + ' isn\'t running'); 75 | } else { 76 | zen.send_admin_message('restarting ' + service); 77 | services[service].restart(); 78 | } 79 | } 80 | 81 | function stop_service(service) { 82 | if (!services[service] || !services[service].running) { 83 | zen.send_admin_message(service + ' isn\'t running'); 84 | } else { 85 | zen.send_admin_message('stopping ' + service); 86 | services[service].stop(); 87 | } 88 | } 89 | 90 | function git_pull() { 91 | zen.send_admin_message('pulling down new code'); 92 | exec("git pull", puts); 93 | } 94 | -------------------------------------------------------------------------------- /services/admin.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "services": ["semantics.js", "troll.js"] 3 | } 4 | -------------------------------------------------------------------------------- /services/funday.py: -------------------------------------------------------------------------------- 1 | import json 2 | from zenircbot_api import ZenIRCBot, load_config 3 | import requests 4 | 5 | 6 | bot_config = load_config('../bot.json') 7 | admins = bot_config['servers'][0]['admin_nicks'] 8 | zen = ZenIRCBot(bot_config['redis']['host'], 9 | bot_config['redis']['port'], 10 | bot_config['redis']['db']) 11 | 12 | commands = [ 13 | { 14 | 'name': '!funday', 15 | 'description': 'Gets a random funday monday from fundayroulette.com' 16 | } 17 | ] 18 | 19 | # zen.register_commands(__file__, commands) 20 | 21 | funday_url = 'http://fundayroulette.com/api/v1/funday/random/?format=json' 22 | 23 | 24 | def debug(data): 25 | return 26 | zen.send_privmsg('#pdxbots', str(data)) 27 | 28 | sub = zen.get_redis_client().pubsub() 29 | sub.subscribe('in') 30 | for msg in sub.listen(): 31 | debug(msg) 32 | if msg['type'] == 'subscribe': 33 | continue 34 | message = json.loads(msg['data']) 35 | if message['version'] == 1: 36 | debug('version==1') 37 | if message['type'] == 'directed_privmsg': 38 | debug('directed') 39 | data = message['data'] 40 | if data['message'] == 'funday': 41 | debug('funday') 42 | res = requests.get(funday_url) 43 | funday = json.loads(res.content) 44 | zen.send_privmsg(data['channel'], 45 | '%s: %s' % (funday['name'], 46 | funday['description'])) 47 | -------------------------------------------------------------------------------- /services/github.js: -------------------------------------------------------------------------------- 1 | var api = require('zenircbot-api'); 2 | var bot_config = api.load_config('../bot.json'); 3 | var zen = new api.ZenIRCBot(bot_config.redis.host, 4 | bot_config.redis.port, 5 | bot_config.redis.db); 6 | var sub = zen.get_redis_client(); 7 | var color = require('./lib/colors'); 8 | var github_config = api.load_config('./github.json'); 9 | 10 | 11 | zen.register_commands("github.js", []); 12 | 13 | sub.subscribe('web_in'); 14 | sub.on('message', function(channel, message){ 15 | message = JSON.parse(message); 16 | if (message.app != 'github') { 17 | return null; 18 | } 19 | var github_json = JSON.parse(message.body.payload); 20 | 21 | var branch = github_json.ref.substr(11); 22 | var repo = github_json.repository.name; 23 | var name_str = ''; 24 | for (var i=0; i< github_json.commits.length; i++) { 25 | var commit = github_json.commits[i]; 26 | if (commit.author.username) { 27 | name_str = ' - ' + commit.author.username + ' (' + commit.author.name + ')'; 28 | } else if (commit.author.name) { 29 | name_str = ' - ' + commit.author.name; 30 | } else { 31 | name_str = ''; 32 | } 33 | message = repo + ': ' + commit.id.substr(0,7) + ' *' + color.green + branch + color.reset +'* ' + commit.message + name_str; 34 | zen.send_privmsg(github_config.channels, message); 35 | console.log(branch + ': ' + commit.author.username); 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /services/github.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "channels": ["#pdxbots"] 3 | } 4 | 5 | -------------------------------------------------------------------------------- /services/jira.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "jira_url": "https://jira.example.com/", 3 | "feed_url": "https://username:password@jira.example.com/plugins/servlet/streams", 4 | "channels": ["#channel"], 5 | "poll_rate": 10 6 | } 7 | -------------------------------------------------------------------------------- /services/jira_feed.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from time import sleep 3 | import re 4 | 5 | from BeautifulSoup import BeautifulSoup 6 | from feedparser import parse 7 | from zenircbot_api import ZenIRCBot, load_config 8 | 9 | 10 | bot_config = load_config('../bot.json') 11 | 12 | zen = ZenIRCBot(bot_config['redis']['host'], 13 | bot_config['redis']['port'], 14 | bot_config['redis']['db']) 15 | 16 | 17 | zen.register_commands('jira_feed.py', []) 18 | jira_config = load_config("./jira.json") 19 | jira_url = '%sbrowse/\\1' % jira_config['jira_url'] 20 | latest = None 21 | 22 | 23 | def strtodt(string): 24 | return datetime.strptime(string, '%Y-%m-%dT%H:%M:%SZ') 25 | 26 | 27 | while True: 28 | feed = parse(jira_config['feed_url']) 29 | if latest is None: 30 | latest = strtodt(feed['entries'][0].updated) - timedelta(seconds=1) 31 | entries = [entry for entry in feed['entries'] 32 | if strtodt(entry.updated) > latest] 33 | for entry in entries: 34 | if strtodt(entry.updated) > latest: 35 | latest = strtodt(entry.updated) 36 | bs = BeautifulSoup(entry.title) 37 | message = ''.join(bs.findAll(text=True)) 38 | if not ('created' in message or 39 | 'resolved' in message or 40 | 'reopened' in message): 41 | continue 42 | zen.send_privmsg(jira_config['channels'], 43 | 'JIRA - %s' % re.sub('(?:\s|^)([a-zA-Z][a-zA-Z]-\d+)', 44 | jira_url, 45 | message)) 46 | 47 | sleep(jira_config['poll_rate']) 48 | -------------------------------------------------------------------------------- /services/jira_ticket.js: -------------------------------------------------------------------------------- 1 | var api = require('zenircbot-api'); 2 | var bot_config = api.load_config('../bot.json'); 3 | var zen = new api.ZenIRCBot(bot_config.redis.host, 4 | bot_config.redis.port, 5 | bot_config.redis.db); 6 | var sub = zen.get_redis_client(); 7 | var ticket = /(?:\s|^)([a-zA-Z][a-zA-Z]-\d+)/g; 8 | var config = api.load_config('./jira.json'); 9 | 10 | 11 | zen.register_commands('jira_ticket.js', []); 12 | 13 | sub.subscribe('in'); 14 | sub.on('message', function(channel, message){ 15 | var msg = JSON.parse(message); 16 | if (msg.version == 1 && msg.type == 'privmsg') { 17 | if (config.channels.indexOf(msg.data.channel) != -1) { 18 | var result = ticket.exec(msg.data.message); 19 | while (result) { 20 | console.log(result[1]); 21 | zen.send_privmsg(config.channels, 22 | config.jira_url + 'browse/' + result[1]); 23 | lastIndex = ticket.lastIndex; 24 | result = ticket.exec(msg.data.message); 25 | } 26 | } 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /services/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenirc/zenircbot/bd9974f6dc27716a6aebd51cac882fc3a326b7b6/services/lib/__init__.py -------------------------------------------------------------------------------- /services/lib/api.js: -------------------------------------------------------------------------------- 1 | console.log('Using lib/api.js is deprecated, please use `npm install zenircbot-api` instead'); 2 | 3 | var fs = require('fs'); 4 | var redis_lib = require('redis'); 5 | var pub = null; 6 | 7 | 8 | function send_privmsg(to, message) { 9 | if (!pub) { 10 | pub = get_redis_client(); 11 | } 12 | if (typeof(to) == "string") { 13 | to = [to] 14 | } 15 | to.forEach(function(destination) { 16 | pub.publish('out', JSON.stringify({ 17 | version: 1, 18 | type: 'privmsg', 19 | data: { 20 | to: destination, 21 | message: message 22 | } 23 | })) 24 | }); 25 | } 26 | 27 | function send_admin_message(message) { 28 | var config = load_config('../bot.json'); 29 | send_privmsg(config.servers[0].admin_spew_channels, message); 30 | } 31 | 32 | function register_commands(service, commands) { 33 | send_admin_message(service + " online!") 34 | sub = get_redis_client(); 35 | sub.subscribe('in'); 36 | sub.on('message', function(channel, message){ 37 | msg = JSON.parse(message) 38 | if (msg.version == 1 && msg.type == 'privmsg') { 39 | if (msg.data.message == "commands") { 40 | commands.forEach( function(command, index) { 41 | send_privmsg(msg.data.sender, 42 | service + ": " + 43 | command.name + " - " + 44 | command.description); 45 | }); 46 | } 47 | } 48 | }); 49 | return sub 50 | } 51 | 52 | function load_config(name) { 53 | return JSON.parse(fs.readFileSync(name, 'utf8')); 54 | } 55 | 56 | function get_redis_client(redis_config) { 57 | if (!redis_config) { 58 | redis_config = load_config('../bot.json').redis; 59 | } 60 | return redis_lib.createClient(redis_config.port, 61 | redis_config.host, { 62 | selected_db: redis_config.db 63 | }); 64 | } 65 | 66 | module.exports = { 67 | send_privmsg: send_privmsg, 68 | send_admin_message: send_admin_message, 69 | register_commands: register_commands, 70 | load_config: load_config, 71 | get_redis_client: get_redis_client 72 | } 73 | -------------------------------------------------------------------------------- /services/lib/api.py: -------------------------------------------------------------------------------- 1 | from redis import StrictRedis 2 | import json 3 | from threading import Thread 4 | import warnings 5 | 6 | warnings.warn(('lib/api.py is deprecated, please install zenircbot_api and ' 7 | 'use that instead'), DeprecationWarning) 8 | 9 | 10 | def send_privmsg(to, message): 11 | if isinstance(to, basestring): 12 | to = (to,) 13 | for channel in to: 14 | get_redis_client().publish('out', 15 | json.dumps({ 16 | 'version': 1, 17 | 'type': 'privmsg', 18 | 'data': { 19 | 'to': channel, 20 | 'message': message, 21 | } 22 | })) 23 | 24 | 25 | def send_admin_message(message): 26 | config = load_config('../bot.json') 27 | send_privmsg(config['servers'][0]['admin_spew_channels'], message) 28 | 29 | 30 | def non_blocking_redis_subscribe(func, args=[], kwargs={}): 31 | pubsub = get_redis_client().pubsub() 32 | pubsub.subscribe('in') 33 | for msg in pubsub.listen(): 34 | message = json.loads(msg['data']) 35 | func(message=message, *args, **kwargs) 36 | 37 | 38 | def register_commands(service, commands): 39 | send_admin_message(service + ' online!') 40 | if commands: 41 | def registration_reply(message, service, commands): 42 | if message['version'] == 1 and message['type'] == 'privmsg': 43 | if message['data']['message'] == "commands": 44 | for command in commands: 45 | send_privmsg(message['data']['sender'], 46 | "%s: %s - %s" % (service, 47 | command['name'], 48 | command['description'])) 49 | redis_sub = Thread(target=non_blocking_redis_subscribe, 50 | kwargs={'func': registration_reply, 51 | 'kwargs': {'service': service, 52 | 'commands': commands}}) 53 | redis_sub.start() 54 | 55 | 56 | def load_config(name): 57 | with open(name) as f: 58 | return json.loads(f.read()) 59 | 60 | 61 | def get_redis_client(redis_config=None): 62 | if not redis_config: 63 | redis_config = load_config('../bot.json')['redis'] 64 | return StrictRedis(host=redis_config['host'], 65 | port=redis_config['port'], 66 | db=redis_config['db']) 67 | -------------------------------------------------------------------------------- /services/lib/colors.js: -------------------------------------------------------------------------------- 1 | colors = { 2 | green: '\u000303', 3 | reset: '\u000f' 4 | } 5 | 6 | module.exports = colors; 7 | -------------------------------------------------------------------------------- /services/motivate.js: -------------------------------------------------------------------------------- 1 | var ZenIRCBot = require('zenircbot-api').ZenIRCBot 2 | var zen = new ZenIRCBot() 3 | var sub = zen.get_redis_client() 4 | 5 | 6 | zen.register_commands( 7 | "motivate.js", 8 | [ 9 | { 10 | name: "m ", 11 | description: "Motivates the target." 12 | }, { 13 | name: "dm ", 14 | description: "Demotivates the target." 15 | } 16 | ] 17 | ) 18 | 19 | 20 | sub.subscribe('in'); 21 | sub.on('message', function(channel, message){ 22 | var msg = JSON.parse(message); 23 | if (msg.version == 1) { 24 | if (msg.type == 'directed_privmsg') { 25 | var motivate = /^m (.*)/i.exec(msg.data.message) 26 | var demotivate = /^dm (.*)/i.exec(msg.data.message) 27 | var validOne = (motivate || demotivate) 28 | if (validOne && validOne[0].trim()) { 29 | if (motivate) { 30 | zen.send_privmsg(msg.data.channel, 31 | "You're doing great work " + 32 | motivate[1].trim() + "!"); 33 | } else if (demotivate) { 34 | zen.send_privmsg(msg.data.channel, 35 | "You're doing horrible work " + 36 | demotivate[1].trim() + "!"); 37 | } 38 | } 39 | } 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /services/release.js: -------------------------------------------------------------------------------- 1 | var api = require('zenircbot-api'); 2 | var bot_config = api.load_config('../bot.json'); 3 | var zen = new api.ZenIRCBot(bot_config.redis.host, 4 | bot_config.redis.port, 5 | bot_config.redis.db); 6 | var sub = zen.get_redis_client(); 7 | var release_config = api.load_config('./release.json'); 8 | 9 | 10 | zen.register_commands('release.js', []); 11 | 12 | sub.subscribe('web_in'); 13 | sub.on('message', function(channel,message){ 14 | message = JSON.parse(message); 15 | if (message.app != 'release') { 16 | return null; 17 | } 18 | var release_json = JSON.parse(message.body.payload); 19 | zen.send_privmsg(release_config.channels, 20 | 'release of ' + release_json.branch + 21 | ' ' + release_json.status + 22 | ' on ' + release_json.hostname); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /services/release.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "channels": ["#pdxbots"] 3 | } 4 | -------------------------------------------------------------------------------- /services/semantics.js: -------------------------------------------------------------------------------- 1 | var api = require('zenircbot-api'); 2 | var bot_config = api.load_config('../bot.json'); 3 | var zen = new api.ZenIRCBot(bot_config.redis.host, 4 | bot_config.redis.port, 5 | bot_config.redis.db); 6 | var sub = zen.get_redis_client(); 7 | 8 | zen.register_commands('semantics.js', []); 9 | 10 | sub.subscribe('in'); 11 | sub.on('message', function(channel, message) { 12 | var msg = JSON.parse(message); 13 | if (msg.version == 1 && msg.type == 'privmsg') { 14 | zen.redis.get('zenircbot:nick', function(err, nick) { 15 | if (msg.data.message.indexOf(nick + ': ') === 0) { 16 | directed_message(msg.data.message, 17 | msg.data.message.substr(nick.length+2), 18 | msg.data.sender, 19 | msg.data.channel); 20 | } else if (msg.data.message.indexOf('!') === 0) { 21 | directed_message(msg.data.message, 22 | msg.data.message.substr(1), 23 | msg.data.sender, 24 | msg.data.channel); 25 | } else if (msg.data.channel == msg.data.sender) { 26 | directed_message(msg.data.message, 27 | msg.data.message, 28 | msg.data.sender, 29 | msg.data.channel); 30 | 31 | } 32 | }); 33 | } 34 | }); 35 | 36 | function directed_message(raw_message, message, sender, channel) { 37 | zen.redis.publish('in', JSON.stringify({ 38 | version: 1, 39 | type: 'directed_privmsg', 40 | data: { 41 | channel: channel, 42 | raw_message: raw_message, 43 | message: message, 44 | sender: sender 45 | } 46 | })); 47 | } 48 | -------------------------------------------------------------------------------- /services/troll.js: -------------------------------------------------------------------------------- 1 | var ZenIRCBot = require('zenircbot-api').ZenIRCBot 2 | var zen = new ZenIRCBot() 3 | var sub = zen.get_redis_client() 4 | var sourceUrl = 'https://github.com/zenirc/zenircbot' 5 | 6 | zen.register_commands( 7 | 'troll.js', 8 | [ 9 | { 10 | name: 'ls', 11 | description: 'Trolls the user for saying ls' 12 | }, { 13 | name: 'irssi', 14 | description: 'When someone mentions irssi, suggests weechat.' 15 | } 16 | ] 17 | ) 18 | 19 | sub.subscribe('in') 20 | sub.on('message', function(channel, message){ 21 | var msg = JSON.parse(message) 22 | if (msg.version == 1) { 23 | if (msg.type == 'privmsg') { 24 | if (/^ls$/.test(msg.data.message)) { 25 | zen.send_privmsg(msg.data.channel, 26 | msg.data.sender + ': http://is.gd/afolif') 27 | } 28 | } else if (msg.type == 'directed_privmsg') { 29 | var who = ['whoareyou', 'who are you?', 'source'] 30 | if (/^ping$/i.test(msg.data.message)) { 31 | zen.send_privmsg(msg.data.channel, msg.data.sender + ': pong!') 32 | } else if (who.indexOf(msg.data.message) != -1) { 33 | zen.redis.get('zenircbot:nick', function(err, nick) { 34 | zen.send_privmsg(msg.data.channel, 35 | 'I am ' + nick + ', an instance of ' + 36 | 'ZenIRCBot. My source can be found ' + 37 | 'here: ' + sourceUrl 38 | ) 39 | }) 40 | } 41 | } 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /services/twsrs.py: -------------------------------------------------------------------------------- 1 | import random 2 | import json 3 | from zenircbot_api import ZenIRCBot, load_config 4 | 5 | 6 | bot_config = load_config('../bot.json') 7 | 8 | zen = ZenIRCBot(bot_config['redis']['host'], 9 | bot_config['redis']['port'], 10 | bot_config['redis']['db']) 11 | 12 | 13 | zen.register_commands('twsrs.py', [ 14 | { 15 | 'name': 'that\'s what she said', 16 | 'description': ('Replies to "That\'s what she said" with quotes from ' 17 | 'famous women') 18 | } 19 | ]) 20 | 21 | quote_list = open('twsrs_quotes.txt').readlines() 22 | 23 | 24 | def get_quote(): 25 | """get_quote""" 26 | index = random.randint(0, len(quote_list) - 1) 27 | quote = quote_list[index].strip() 28 | return quote 29 | 30 | 31 | sub = zen.get_redis_client().pubsub() 32 | sub.subscribe('in') 33 | for msg in sub.listen(): 34 | message = json.loads(msg['data']) 35 | if message['version'] == 1: 36 | if message['type'] == 'privmsg': 37 | text = message['data']['message'] 38 | # Change to ascii and drop unicode chars. 39 | text = text.encode('ascii', 'ignore').lower() 40 | # Drop other chars that we don't like.' 41 | text = text.translate(None, """`~!@#$%^&*()_-+={}[];:'"<>,.?/""") 42 | if text.startswith('thats what she said'): 43 | zen.send_privmsg(message['data']['channel'], get_quote()) 44 | elif message['type'] == 'directed_privmsg': 45 | if message['data']['message'] == 'twsrs': 46 | zen.send_privmsg(message['data']['channel'], get_quote()) 47 | -------------------------------------------------------------------------------- /services/weather.js: -------------------------------------------------------------------------------- 1 | var api = require('zenircbot-api'); 2 | var zen = new api.ZenIRCBot(); 3 | var sub = zen.get_redis_client(); 4 | var weather = require('googleweather'); 5 | 6 | zen.register_commands("weather.js", 7 | [{name: "!weather ", 8 | description: "fetches weather for a given location from Google." 9 | }]); 10 | 11 | var weather_config = api.load_config('./weather.json'); 12 | var default_location = weather_config.location || null; 13 | 14 | sub.subscribe('in'); 15 | sub.on('message', function(channel, message) { 16 | var msg = JSON.parse(message); 17 | var sender = msg.data.sender; 18 | if (msg.version == 1) { 19 | if (msg.type == "directed_privmsg") { 20 | var match = /weather(?:\s(.+))?/i.exec(msg.data.message); 21 | if (match.length > 0) { 22 | var location = match[1] || default_location; 23 | if (location) { 24 | var current_date = new Date().toDateString(); 25 | weather.get(function(w) { 26 | weather_msg = "Current Weather: " + w.condition + 27 | ", Temperature: " + w.temperature + "ºC" + 28 | ", Humidity: " + w.humidity + "%" + 29 | ", Wind: " + w.wind.direction + 30 | " " + w.wind.speed + "KPH"; 31 | 32 | zen.send_privmsg(msg.data.channel, 33 | sender + ': ' + weather_msg); 34 | }, location, current_date); 35 | } 36 | } 37 | } 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /services/weather.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "location": "97214" 3 | } 4 | -------------------------------------------------------------------------------- /services/weblistener.js: -------------------------------------------------------------------------------- 1 | var api = require('zenircbot-api'); 2 | var bot_config = api.load_config('../bot.json'); 3 | var zen = new api.ZenIRCBot(bot_config.redis.host, 4 | bot_config.redis.port, 5 | bot_config.redis.db); 6 | var weblistener_config = api.load_config('./weblistener.json'); 7 | var express = require('express'); 8 | var app = express.createServer(); 9 | 10 | 11 | zen.register_commands('weblistener.js', []); 12 | 13 | app.use(express.bodyParser()); 14 | 15 | app.post('/:app', function(req, res) { 16 | console.log(req.params.app); 17 | var message = { 18 | app: req.params.app, 19 | body: req.body 20 | }; 21 | zen.redis.publish('web_in', JSON.stringify(message)); 22 | res.send('',404); 23 | }); 24 | 25 | app.listen(weblistener_config.port); 26 | -------------------------------------------------------------------------------- /services/weblistener.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "port": "10000" 3 | } 4 | -------------------------------------------------------------------------------- /tests/circus.ini: -------------------------------------------------------------------------------- 1 | [circus] 2 | check_delay = 5 3 | endpoint = tcp://127.0.0.1:5555 4 | 5 | [watcher:redis] 6 | cmd = redis-server 7 | args = configs/redis.conf 8 | 9 | [watcher:ngircd] 10 | cmd = ngircd 11 | args = -n -f configs/ngircd.conf 12 | 13 | # [watcher:bot] 14 | # cmd = node 15 | # args = ../bot.js -c configs/bot.json -------------------------------------------------------------------------------- /tests/configs/bot.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": [ 3 | { 4 | "name": "testing", 5 | "hostname": "localhost", 6 | "nick": "ZenIRCBot", 7 | "userName": "zenircbot", 8 | "realName": "ZenIRCBot", 9 | "port": 46667, 10 | "autoRejoin": true, 11 | "autoConnect": true, 12 | "channels": ["#pdxbots", "##pdxbots"], 13 | "admin_nicks": ["WraithTest", "Wraithan"], 14 | "admin_spew_channels": ["#pdxbots"], 15 | "secure": false, 16 | "selfSigned": false 17 | } 18 | ], 19 | "options": { 20 | "debug": false, 21 | "floodProtection": true, 22 | "stripColors": false 23 | }, 24 | "redis": { 25 | "host": "localhost", 26 | "port": 46379, 27 | "db": 0 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/configs/ngircd.conf: -------------------------------------------------------------------------------- 1 | # 2 | # This is a sample configuration file for the ngIRCd IRC daemon, which must 3 | # be customized to the local preferences and needs. 4 | # 5 | # Comments are started with "#" or ";". 6 | # 7 | # A lot of configuration options in this file start with a ";". You have 8 | # to remove the ";" in front of each variable to actually set a value! 9 | # The disabled variables are shown with example values for completeness only 10 | # and the daemon is using compiled-in default settings. 11 | # 12 | # Use "ngircd --configtest" (see manual page ngircd(8)) to validate that the 13 | # server interprets the configuration file as expected! 14 | # 15 | # Please see ngircd.conf(5) for a complete list of configuration options 16 | # and their descriptions. 17 | # 18 | 19 | [Global] 20 | # The [Global] section of this file is used to define the main 21 | # configuration of the server, like the server name and the ports 22 | # on which the server should be listening. 23 | # These settings depend on your personal preferences, so you should 24 | # make sure that they correspond to your installation and setup! 25 | 26 | # Server name in the IRC network, must contain at least one dot 27 | # (".") and be unique in the IRC network. Required! 28 | Name = irc.example.net 29 | 30 | # Information about the server and the administrator, used by the 31 | # ADMIN command. Not required by server but by RFC! 32 | ;AdminInfo1 = Description 33 | ;AdminInfo2 = Location 34 | ;AdminEMail = admin@irc.server 35 | 36 | # Info text of the server. This will be shown by WHOIS and 37 | # LINKS requests for example. 38 | Info = Server Info Text 39 | 40 | # Comma separated list of IP addresses on which the server should 41 | # listen. Default values are: 42 | # "0.0.0.0" or (if compiled with IPv6 support) "::,0.0.0.0" 43 | # so the server listens on all IP addresses of the system by default. 44 | ;Listen = 127.0.0.1,192.168.0.1 45 | 46 | # Text file with the "message of the day" (MOTD). This message will 47 | # be shown to all users connecting to the server: 48 | ;MotdFile = /etc/ngircd.motd 49 | 50 | # A simple Phrase (<256 chars) if you don't want to use a motd file. 51 | MotdPhrase = "Hello world!" 52 | 53 | # Global password for all users needed to connect to the server. 54 | # (Default: not set) 55 | ;Password = abc 56 | 57 | # This tells ngIRCd to write its current process ID to a file. 58 | # Note that the pidfile is written AFTER chroot and switching the 59 | # user ID, e.g. the directory the pidfile resides in must be 60 | # writable by the ngIRCd user and exist in the chroot directory. 61 | PidFile = /tmp/ngircd.pid 62 | 63 | # Ports on which the server should listen. There may be more than 64 | # one port, separated with ",". (Default: 6667) 65 | Ports = 46667 66 | 67 | # Group ID under which the ngIRCd should run; you can use the name 68 | # of the group or the numerical ID. ATTENTION: For this to work the 69 | # server must have been started with root privileges! 70 | ;ServerGID = 65534 71 | 72 | # User ID under which the server should run; you can use the name 73 | # of the user or the numerical ID. ATTENTION: For this to work the 74 | # server must have been started with root privileges! In addition, 75 | # the configuration and MOTD files must be readable by this user, 76 | # otherwise RESTART and REHASH won't work! 77 | ;ServerUID = 65534 78 | 79 | [Limits] 80 | # Define some limits and timeouts for this ngIRCd instance. Default 81 | # values should be safe, but it is wise to double-check :-) 82 | 83 | # The server tries every seconds to establish a link 84 | # to not yet (or no longer) connected servers. 85 | ;ConnectRetry = 60 86 | 87 | # Maximum number of simultaneous in- and outbound connections the 88 | # server is allowed to accept (0: unlimited): 89 | MaxConnections = 0 90 | 91 | # Maximum number of simultaneous connections from a single IP address 92 | # the server will accept (0: unlimited): 93 | MaxConnectionsIP = 0 94 | 95 | # Maximum number of channels a user can be member of (0: no limit): 96 | MaxJoins = 0 97 | 98 | # Maximum length of an user nick name (Default: 9, as in RFC 2812). 99 | # Please note that all servers in an IRC network MUST use the same 100 | # maximum nick name length! 101 | MaxNickLength = 20 102 | 103 | # After seconds of inactivity the server will send a 104 | # PING to the peer to test whether it is alive or not. 105 | ;PingTimeout = 120 106 | 107 | # If a client fails to answer a PING with a PONG within 108 | # seconds, it will be disconnected by the server. 109 | ;PongTimeout = 20 110 | 111 | [Options] 112 | # Optional features and configuration options to further tweak the 113 | # behavior of ngIRCd. If you want to get started quickly, you most 114 | # probably don't have to make changes here -- they are all optional. 115 | 116 | # Are remote IRC operators allowed to control this server, e.g. 117 | # use commands like CONNECT, SQUIT, DIE, ...? 118 | ;AllowRemoteOper = no 119 | 120 | # A directory to chroot in when everything is initialized. It 121 | # doesn't need to be populated if ngIRCd is compiled as a static 122 | # binary. By default ngIRCd won't use the chroot() feature. 123 | # ATTENTION: For this to work the server must have been started 124 | # with root privileges! 125 | ;ChrootDir = /var/empty 126 | 127 | # Set this hostname for every client instead of the real one. 128 | # Please note: don't use the percentage sign ("%"), it is reserved for 129 | # future extensions! 130 | ;CloakHost = irc.example.net 131 | 132 | # Set every clients' user name to their nick name 133 | ;CloakUserToNick = yes 134 | 135 | # Try to connect to other IRC servers using IPv4 and IPv6, if possible. 136 | ;ConnectIPv6 = yes 137 | ;ConnectIPv4 = yes 138 | 139 | # Do any DNS lookups when a client connects to the server. 140 | ;DNS = yes 141 | 142 | # Do IDENT lookups if ngIRCd has been compiled with support for it. 143 | # Users identified using IDENT are registered without the "~" character 144 | # prepended to their user name. 145 | ;Ident = yes 146 | 147 | # Enhance user privacy slightly (useful for IRC server on TOR or I2P) 148 | # by censoring some information like idle time, logon time, etc. 149 | ;MorePrivacy = no 150 | 151 | # Normally ngIRCd doesn't send any messages to a client until it is 152 | # registered. Enable this option to let the daemon send "NOTICE AUTH" 153 | # messages to clients while connecting. 154 | ;NoticeAuth = no 155 | 156 | # Should IRC Operators be allowed to use the MODE command even if 157 | # they are not(!) channel-operators? 158 | ;OperCanUseMode = no 159 | 160 | # Mask IRC Operator mode requests as if they were coming from the 161 | # server? (This is a compatibility hack for ircd-irc2 servers) 162 | ;OperServerMode = no 163 | 164 | # Use PAM if ngIRCd has been compiled with support for it. 165 | # Users identified using PAM are registered without the "~" character 166 | # prepended to their user name. 167 | ;PAM = yes 168 | 169 | # When PAM is enabled, all clients are required to be authenticated 170 | # using PAM; connecting to the server without successful PAM 171 | # authentication isn't possible. 172 | # If this option is set, clients not sending a password are still 173 | # allowed to connect: they won't become "identified" and keep the "~" 174 | # character prepended to their supplied user name. 175 | # Please note: To make some use of this behavior, it most probably 176 | # isn't useful to enable "Ident", "PAM" and "PAMIsOptional" at the 177 | # same time, because you wouldn't be able to distinguish between 178 | # Ident'ified and PAM-authenticated users: both don't have a "~" 179 | # character prepended to their respective user names! 180 | ;PAMIsOptional = no 181 | 182 | # Allow Pre-Defined Channels only (see Section [Channels]) 183 | ;PredefChannelsOnly = no 184 | 185 | # Let ngIRCd send an "authentication PING" when a new client connects, 186 | # and register this client only after receiving the corresponding 187 | # "PONG" reply. 188 | ;RequireAuthPing = no 189 | 190 | # Silently drop all incoming CTCP requests. 191 | ;ScrubCTCP = no 192 | 193 | # Syslog "facility" to which ngIRCd should send log messages. 194 | # Possible values are system dependent, but most probably auth, daemon, 195 | # user and local1 through local7 are possible values; see syslog(3). 196 | # Default is "local5" for historical reasons, you probably want to 197 | # change this to "daemon", for example. 198 | ;SyslogFacility = local1 199 | 200 | # Password required for using the WEBIRC command used by some 201 | # Web-to-IRC gateways. If not set/empty, the WEBIRC command can't 202 | # be used. (Default: not set) 203 | ;WebircPassword = xyz 204 | 205 | ;[SSL] 206 | # SSL-related configuration options. Please note that this section 207 | # is only available when ngIRCd is compiled with support for SSL! 208 | # So don't forget to remove the ";" above if this is the case ... 209 | 210 | # SSL Server Key Certificate 211 | ;CertFile = /etc/ssl/server-cert.pem 212 | 213 | # Diffie-Hellman parameters 214 | ;DHFile = /etc/ssl/dhparams.pem 215 | 216 | # SSL Server Key 217 | ;KeyFile = /etc/ssl/server-key.pem 218 | 219 | # password to decrypt SSLKeyFile (OpenSSL only) 220 | ;KeyFilePassword = secret 221 | 222 | # Additional Listen Ports that expect SSL/TLS encrypted connections 223 | ;Ports = 6697, 9999 224 | 225 | [Operator] 226 | # [Operator] sections are used to define IRC Operators. There may be 227 | # more than one [Operator] block, one for each local operator. 228 | 229 | # ID of the operator (may be different of the nick name) 230 | ;Name = TheOper 231 | 232 | # Password of the IRC operator 233 | ;Password = ThePwd 234 | 235 | # Optional Mask from which /OPER will be accepted 236 | ;Mask = *!ident@somewhere.example.com 237 | 238 | [Operator] 239 | # More [Operator] sections, if you like ... 240 | 241 | [Server] 242 | # Other servers are configured in [Server] sections. If you 243 | # configure a port for the connection, then this ngircd tries to 244 | # connect to to the other server on the given port; if not it waits 245 | # for the other server to connect. 246 | # There may be more than one server block, one for each server. 247 | # 248 | # Server Groups: 249 | # The ngIRCd allows "server groups": You can assign an "ID" to every 250 | # server with which you want this ngIRCd to link. If a server of a 251 | # group won't answer, the ngIRCd tries to connect to the next server 252 | # in the given group. But the ngircd never tries to connect to two 253 | # servers with the same group ID. 254 | 255 | # IRC name of the remote server, must match the "Name" variable in 256 | # the [Global] section of the other server (when using ngIRCd). 257 | ;Name = irc2.example.net 258 | 259 | # Internet host name or IP address of the peer (only required when 260 | # this server should establish the connection). 261 | ;Host = connect-to-host.example.net 262 | 263 | # IP address to use as _source_ address for the connection. if 264 | # unspecified, ngircd will let the operating system pick an address. 265 | ;Bind = 10.0.0.1 266 | 267 | # Port of the server to which the ngIRCd should connect. If you 268 | # assign no port the ngIRCd waits for incoming connections. 269 | ;Port = 6667 270 | 271 | # Own password for the connection. This password has to be configured 272 | # as "PeerPassword" on the other server. 273 | ;MyPassword = MySecret 274 | 275 | # Foreign password for this connection. This password has to be 276 | # configured as "MyPassword" on the other server. 277 | ;PeerPassword = PeerSecret 278 | 279 | # Group of this server (optional) 280 | ;Group = 123 281 | 282 | # Set the "Passive" option to "yes" if you don't want this ngIRCd to 283 | # connect to the configured peer (same as leaving the "Port" variable 284 | # empty). The advantage of this option is that you can actually 285 | # configure a port an use the IRC command CONNECT more easily to 286 | # manually connect this specific server later. 287 | ;Passive = no 288 | 289 | # Connect to the remote server using TLS/SSL (Default: false) 290 | ;SSLConnect = yes 291 | 292 | # Define a (case insensitive) mask matching nick names that should be 293 | # treated as IRC services when introduced via this remote server. 294 | # REGULAR SERVERS DON'T NEED this parameter, so leave it empty 295 | # (which is the default). 296 | # When you are connecting IRC services which mask as a IRC server 297 | # and which use "virtual users" to communicate with, for example 298 | # "NickServ" and "ChanServ", you should set this parameter to 299 | # something like "*Serv". 300 | ;ServiceMask = *Serv 301 | 302 | [Server] 303 | # More [Server] sections, if you like ... 304 | 305 | [Channel] 306 | # Pre-defined channels can be configured in [Channel] sections. 307 | # Such channels are created by the server when starting up and even 308 | # persist when there are no more members left. 309 | # Persistent channels are marked with the mode 'P', which can be set 310 | # and unset by IRC operators like other modes on the fly. 311 | # There may be more than one [Channel] block, one for each channel. 312 | 313 | # Name of the channel 314 | ;Name = #TheName 315 | 316 | # Topic for this channel 317 | ;Topic = a great topic 318 | 319 | # Initial channel modes 320 | ;Modes = tnk 321 | 322 | # initial channel password (mode k) 323 | ;Key = Secret 324 | 325 | # Key file, syntax for each line: "::". 326 | # Default: none. 327 | ;KeyFile = /etc/#chan.key 328 | 329 | # maximum users per channel (mode l) 330 | ;MaxUsers = 23 331 | 332 | [Channel] 333 | # More [Channel] sections, if you like ... 334 | 335 | # -eof- 336 | -------------------------------------------------------------------------------- /tests/configs/redis.conf: -------------------------------------------------------------------------------- 1 | # Redis configuration file example 2 | 3 | # Note on units: when memory size is needed, it is possible to specifiy 4 | # it in the usual form of 1k 5GB 4M and so forth: 5 | # 6 | # 1k => 1000 bytes 7 | # 1kb => 1024 bytes 8 | # 1m => 1000000 bytes 9 | # 1mb => 1024*1024 bytes 10 | # 1g => 1000000000 bytes 11 | # 1gb => 1024*1024*1024 bytes 12 | # 13 | # units are case insensitive so 1GB 1Gb 1gB are all the same. 14 | 15 | # By default Redis does not run as a daemon. Use 'yes' if you need it. 16 | # Note that Redis will write a pid file in /var/run/redis.pid when daemonized. 17 | daemonize no 18 | 19 | # When running daemonized, Redis writes a pid file in /var/run/redis.pid by 20 | # default. You can specify a custom pid file location here. 21 | pidfile /tmp/redis.pid 22 | 23 | # Accept connections on the specified port, default is 6379. 24 | # If port 0 is specified Redis will not listen on a TCP socket. 25 | port 46379 26 | 27 | # If you want you can bind a single interface, if the bind option is not 28 | # specified all the interfaces will listen for incoming connections. 29 | # 30 | # bind 127.0.0.1 31 | 32 | # Specify the path for the unix socket that will be used to listen for 33 | # incoming connections. There is no default, so Redis will not listen 34 | # on a unix socket when not specified. 35 | # 36 | # unixsocket /tmp/redis.sock 37 | # unixsocketperm 755 38 | 39 | # Close the connection after a client is idle for N seconds (0 to disable) 40 | timeout 0 41 | 42 | # Set server verbosity to 'debug' 43 | # it can be one of: 44 | # debug (a lot of information, useful for development/testing) 45 | # verbose (many rarely useful info, but not a mess like the debug level) 46 | # notice (moderately verbose, what you want in production probably) 47 | # warning (only very important / critical messages are logged) 48 | loglevel verbose 49 | 50 | # Specify the log file name. Also 'stdout' can be used to force 51 | # Redis to log on the standard output. Note that if you use standard 52 | # output for logging but daemonize, logs will be sent to /dev/null 53 | logfile var/log/redis.log 54 | 55 | # To enable logging to the system logger, just set 'syslog-enabled' to yes, 56 | # and optionally update the other syslog parameters to suit your needs. 57 | # syslog-enabled no 58 | 59 | # Specify the syslog identity. 60 | # syslog-ident redis 61 | 62 | # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. 63 | # syslog-facility local0 64 | 65 | # Set the number of databases. The default database is DB 0, you can select 66 | # a different one on a per-connection basis using SELECT where 67 | # dbid is a number between 0 and 'databases'-1 68 | databases 16 69 | 70 | ################################ SNAPSHOTTING ################################# 71 | # 72 | # Save the DB on disk: 73 | # 74 | # save 75 | # 76 | # Will save the DB if both the given number of seconds and the given 77 | # number of write operations against the DB occurred. 78 | # 79 | # In the example below the behaviour will be to save: 80 | # after 900 sec (15 min) if at least 1 key changed 81 | # after 300 sec (5 min) if at least 10 keys changed 82 | # after 60 sec if at least 10000 keys changed 83 | # 84 | # Note: you can disable saving at all commenting all the "save" lines. 85 | 86 | save 900 1 87 | save 300 10 88 | save 60 10000 89 | 90 | # Compress string objects using LZF when dump .rdb databases? 91 | # For default that's set to 'yes' as it's almost always a win. 92 | # If you want to save some CPU in the saving child set it to 'no' but 93 | # the dataset will likely be bigger if you have compressible values or keys. 94 | rdbcompression yes 95 | 96 | # The filename where to dump the DB 97 | dbfilename dump.rdb 98 | 99 | # The working directory. 100 | # 101 | # The DB will be written inside this directory, with the filename specified 102 | # above using the 'dbfilename' configuration directive. 103 | # 104 | # Also the Append Only File will be created inside this directory. 105 | # 106 | # Note that you must specify a directory here, not a file name. 107 | dir var/lib/redis/ 108 | 109 | ################################# REPLICATION ################################# 110 | 111 | # Master-Slave replication. Use slaveof to make a Redis instance a copy of 112 | # another Redis server. Note that the configuration is local to the slave 113 | # so for example it is possible to configure the slave to save the DB with a 114 | # different interval, or to listen to another port, and so on. 115 | # 116 | # slaveof 117 | 118 | # If the master is password protected (using the "requirepass" configuration 119 | # directive below) it is possible to tell the slave to authenticate before 120 | # starting the replication synchronization process, otherwise the master will 121 | # refuse the slave request. 122 | # 123 | # masterauth 124 | 125 | # When a slave lost the connection with the master, or when the replication 126 | # is still in progress, the slave can act in two different ways: 127 | # 128 | # 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will 129 | # still reply to client requests, possibly with out of data data, or the 130 | # data set may just be empty if this is the first synchronization. 131 | # 132 | # 2) if slave-serve-stale data is set to 'no' the slave will reply with 133 | # an error "SYNC with master in progress" to all the kind of commands 134 | # but to INFO and SLAVEOF. 135 | # 136 | slave-serve-stale-data yes 137 | 138 | # Slaves send PINGs to server in a predefined interval. It's possible to change 139 | # this interval with the repl_ping_slave_period option. The default value is 10 140 | # seconds. 141 | # 142 | # repl-ping-slave-period 10 143 | 144 | # The following option sets a timeout for both Bulk transfer I/O timeout and 145 | # master data or ping response timeout. The default value is 60 seconds. 146 | # 147 | # It is important to make sure that this value is greater than the value 148 | # specified for repl-ping-slave-period otherwise a timeout will be detected 149 | # every time there is low traffic between the master and the slave. 150 | # 151 | # repl-timeout 60 152 | 153 | ################################## SECURITY ################################### 154 | 155 | # Require clients to issue AUTH before processing any other 156 | # commands. This might be useful in environments in which you do not trust 157 | # others with access to the host running redis-server. 158 | # 159 | # This should stay commented out for backward compatibility and because most 160 | # people do not need auth (e.g. they run their own servers). 161 | # 162 | # Warning: since Redis is pretty fast an outside user can try up to 163 | # 150k passwords per second against a good box. This means that you should 164 | # use a very strong password otherwise it will be very easy to break. 165 | # 166 | # requirepass foobared 167 | 168 | # Command renaming. 169 | # 170 | # It is possilbe to change the name of dangerous commands in a shared 171 | # environment. For instance the CONFIG command may be renamed into something 172 | # of hard to guess so that it will be still available for internal-use 173 | # tools but not available for general clients. 174 | # 175 | # Example: 176 | # 177 | # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 178 | # 179 | # It is also possilbe to completely kill a command renaming it into 180 | # an empty string: 181 | # 182 | # rename-command CONFIG "" 183 | 184 | ################################### LIMITS #################################### 185 | 186 | # Set the max number of connected clients at the same time. By default there 187 | # is no limit, and it's up to the number of file descriptors the Redis process 188 | # is able to open. The special value '0' means no limits. 189 | # Once the limit is reached Redis will close all the new connections sending 190 | # an error 'max number of clients reached'. 191 | # 192 | # maxclients 128 193 | 194 | # Don't use more memory than the specified amount of bytes. 195 | # When the memory limit is reached Redis will try to remove keys 196 | # accordingly to the eviction policy selected (see maxmemmory-policy). 197 | # 198 | # If Redis can't remove keys according to the policy, or if the policy is 199 | # set to 'noeviction', Redis will start to reply with errors to commands 200 | # that would use more memory, like SET, LPUSH, and so on, and will continue 201 | # to reply to read-only commands like GET. 202 | # 203 | # This option is usually useful when using Redis as an LRU cache, or to set 204 | # an hard memory limit for an instance (using the 'noeviction' policy). 205 | # 206 | # WARNING: If you have slaves attached to an instance with maxmemory on, 207 | # the size of the output buffers needed to feed the slaves are subtracted 208 | # from the used memory count, so that network problems / resyncs will 209 | # not trigger a loop where keys are evicted, and in turn the output 210 | # buffer of slaves is full with DELs of keys evicted triggering the deletion 211 | # of more keys, and so forth until the database is completely emptied. 212 | # 213 | # In short... if you have slaves attached it is suggested that you set a lower 214 | # limit for maxmemory so that there is some free RAM on the system for slave 215 | # output buffers (but this is not needed if the policy is 'noeviction'). 216 | # 217 | # maxmemory 218 | 219 | # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory 220 | # is reached? You can select among five behavior: 221 | # 222 | # volatile-lru -> remove the key with an expire set using an LRU algorithm 223 | # allkeys-lru -> remove any key accordingly to the LRU algorithm 224 | # volatile-random -> remove a random key with an expire set 225 | # allkeys->random -> remove a random key, any key 226 | # volatile-ttl -> remove the key with the nearest expire time (minor TTL) 227 | # noeviction -> don't expire at all, just return an error on write operations 228 | # 229 | # Note: with all the kind of policies, Redis will return an error on write 230 | # operations, when there are not suitable keys for eviction. 231 | # 232 | # At the date of writing this commands are: set setnx setex append 233 | # incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd 234 | # sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby 235 | # zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby 236 | # getset mset msetnx exec sort 237 | # 238 | # The default is: 239 | # 240 | # maxmemory-policy volatile-lru 241 | 242 | # LRU and minimal TTL algorithms are not precise algorithms but approximated 243 | # algorithms (in order to save memory), so you can select as well the sample 244 | # size to check. For instance for default Redis will check three keys and 245 | # pick the one that was used less recently, you can change the sample size 246 | # using the following configuration directive. 247 | # 248 | # maxmemory-samples 3 249 | 250 | ############################## APPEND ONLY MODE ############################### 251 | 252 | # By default Redis asynchronously dumps the dataset on disk. If you can live 253 | # with the idea that the latest records will be lost if something like a crash 254 | # happens this is the preferred way to run Redis. If instead you care a lot 255 | # about your data and don't want to that a single record can get lost you should 256 | # enable the append only mode: when this mode is enabled Redis will append 257 | # every write operation received in the file appendonly.aof. This file will 258 | # be read on startup in order to rebuild the full dataset in memory. 259 | # 260 | # Note that you can have both the async dumps and the append only file if you 261 | # like (you have to comment the "save" statements above to disable the dumps). 262 | # Still if append only mode is enabled Redis will load the data from the 263 | # log file at startup ignoring the dump.rdb file. 264 | # 265 | # IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append 266 | # log file in background when it gets too big. 267 | 268 | appendonly no 269 | 270 | # The name of the append only file (default: "appendonly.aof") 271 | # appendfilename appendonly.aof 272 | 273 | # The fsync() call tells the Operating System to actually write data on disk 274 | # instead to wait for more data in the output buffer. Some OS will really flush 275 | # data on disk, some other OS will just try to do it ASAP. 276 | # 277 | # Redis supports three different modes: 278 | # 279 | # no: don't fsync, just let the OS flush the data when it wants. Faster. 280 | # always: fsync after every write to the append only log . Slow, Safest. 281 | # everysec: fsync only if one second passed since the last fsync. Compromise. 282 | # 283 | # The default is "everysec" that's usually the right compromise between 284 | # speed and data safety. It's up to you to understand if you can relax this to 285 | # "no" that will will let the operating system flush the output buffer when 286 | # it wants, for better performances (but if you can live with the idea of 287 | # some data loss consider the default persistence mode that's snapshotting), 288 | # or on the contrary, use "always" that's very slow but a bit safer than 289 | # everysec. 290 | # 291 | # If unsure, use "everysec". 292 | 293 | # appendfsync always 294 | appendfsync everysec 295 | # appendfsync no 296 | 297 | # When the AOF fsync policy is set to always or everysec, and a background 298 | # saving process (a background save or AOF log background rewriting) is 299 | # performing a lot of I/O against the disk, in some Linux configurations 300 | # Redis may block too long on the fsync() call. Note that there is no fix for 301 | # this currently, as even performing fsync in a different thread will block 302 | # our synchronous write(2) call. 303 | # 304 | # In order to mitigate this problem it's possible to use the following option 305 | # that will prevent fsync() from being called in the main process while a 306 | # BGSAVE or BGREWRITEAOF is in progress. 307 | # 308 | # This means that while another child is saving the durability of Redis is 309 | # the same as "appendfsync none", that in pratical terms means that it is 310 | # possible to lost up to 30 seconds of log in the worst scenario (with the 311 | # default Linux settings). 312 | # 313 | # If you have latency problems turn this to "yes". Otherwise leave it as 314 | # "no" that is the safest pick from the point of view of durability. 315 | no-appendfsync-on-rewrite no 316 | 317 | # Automatic rewrite of the append only file. 318 | # Redis is able to automatically rewrite the log file implicitly calling 319 | # BGREWRITEAOF when the AOF log size will growth by the specified percentage. 320 | # 321 | # This is how it works: Redis remembers the size of the AOF file after the 322 | # latest rewrite (or if no rewrite happened since the restart, the size of 323 | # the AOF at startup is used). 324 | # 325 | # This base size is compared to the current size. If the current size is 326 | # bigger than the specified percentage, the rewrite is triggered. Also 327 | # you need to specify a minimal size for the AOF file to be rewritten, this 328 | # is useful to avoid rewriting the AOF file even if the percentage increase 329 | # is reached but it is still pretty small. 330 | # 331 | # Specify a precentage of zero in order to disable the automatic AOF 332 | # rewrite feature. 333 | 334 | auto-aof-rewrite-percentage 100 335 | auto-aof-rewrite-min-size 64mb 336 | 337 | ################################## SLOW LOG ################################### 338 | 339 | # The Redis Slow Log is a system to log queries that exceeded a specified 340 | # execution time. The execution time does not include the I/O operations 341 | # like talking with the client, sending the reply and so forth, 342 | # but just the time needed to actually execute the command (this is the only 343 | # stage of command execution where the thread is blocked and can not serve 344 | # other requests in the meantime). 345 | # 346 | # You can configure the slow log with two parameters: one tells Redis 347 | # what is the execution time, in microseconds, to exceed in order for the 348 | # command to get logged, and the other parameter is the length of the 349 | # slow log. When a new command is logged the oldest one is removed from the 350 | # queue of logged commands. 351 | 352 | # The following time is expressed in microseconds, so 1000000 is equivalent 353 | # to one second. Note that a negative number disables the slow log, while 354 | # a value of zero forces the logging of every command. 355 | slowlog-log-slower-than 10000 356 | 357 | # There is no limit to this length. Just be aware that it will consume memory. 358 | # You can reclaim memory used by the slow log with SLOWLOG RESET. 359 | slowlog-max-len 1024 360 | 361 | ################################ VIRTUAL MEMORY ############################### 362 | 363 | ### WARNING! Virtual Memory is deprecated in Redis 2.4 364 | ### The use of Virtual Memory is strongly discouraged. 365 | 366 | # Virtual Memory allows Redis to work with datasets bigger than the actual 367 | # amount of RAM needed to hold the whole dataset in memory. 368 | # In order to do so very used keys are taken in memory while the other keys 369 | # are swapped into a swap file, similarly to what operating systems do 370 | # with memory pages. 371 | # 372 | # To enable VM just set 'vm-enabled' to yes, and set the following three 373 | # VM parameters accordingly to your needs. 374 | 375 | vm-enabled no 376 | # vm-enabled yes 377 | 378 | # This is the path of the Redis swap file. As you can guess, swap files 379 | # can't be shared by different Redis instances, so make sure to use a swap 380 | # file for every redis process you are running. Redis will complain if the 381 | # swap file is already in use. 382 | # 383 | # The best kind of storage for the Redis swap file (that's accessed at random) 384 | # is a Solid State Disk (SSD). 385 | # 386 | # *** WARNING *** if you are using a shared hosting the default of putting 387 | # the swap file under /tmp is not secure. Create a dir with access granted 388 | # only to Redis user and configure Redis to create the swap file there. 389 | vm-swap-file /tmp/redis.swap 390 | 391 | # vm-max-memory configures the VM to use at max the specified amount of 392 | # RAM. Everything that deos not fit will be swapped on disk *if* possible, that 393 | # is, if there is still enough contiguous space in the swap file. 394 | # 395 | # With vm-max-memory 0 the system will swap everything it can. Not a good 396 | # default, just specify the max amount of RAM you can in bytes, but it's 397 | # better to leave some margin. For instance specify an amount of RAM 398 | # that's more or less between 60 and 80% of your free RAM. 399 | vm-max-memory 0 400 | 401 | # Redis swap files is split into pages. An object can be saved using multiple 402 | # contiguous pages, but pages can't be shared between different objects. 403 | # So if your page is too big, small objects swapped out on disk will waste 404 | # a lot of space. If you page is too small, there is less space in the swap 405 | # file (assuming you configured the same number of total swap file pages). 406 | # 407 | # If you use a lot of small objects, use a page size of 64 or 32 bytes. 408 | # If you use a lot of big objects, use a bigger page size. 409 | # If unsure, use the default :) 410 | vm-page-size 32 411 | 412 | # Number of total memory pages in the swap file. 413 | # Given that the page table (a bitmap of free/used pages) is taken in memory, 414 | # every 8 pages on disk will consume 1 byte of RAM. 415 | # 416 | # The total swap size is vm-page-size * vm-pages 417 | # 418 | # With the default of 32-bytes memory pages and 134217728 pages Redis will 419 | # use a 4 GB swap file, that will use 16 MB of RAM for the page table. 420 | # 421 | # It's better to use the smallest acceptable value for your application, 422 | # but the default is large in order to work in most conditions. 423 | vm-pages 134217728 424 | 425 | # Max number of VM I/O threads running at the same time. 426 | # This threads are used to read/write data from/to swap file, since they 427 | # also encode and decode objects from disk to memory or the reverse, a bigger 428 | # number of threads can help with big objects even if they can't help with 429 | # I/O itself as the physical device may not be able to couple with many 430 | # reads/writes operations at the same time. 431 | # 432 | # The special value of 0 turn off threaded I/O and enables the blocking 433 | # Virtual Memory implementation. 434 | vm-max-threads 4 435 | 436 | ############################### ADVANCED CONFIG ############################### 437 | 438 | # Hashes are encoded in a special way (much more memory efficient) when they 439 | # have at max a given numer of elements, and the biggest element does not 440 | # exceed a given threshold. You can configure this limits with the following 441 | # configuration directives. 442 | hash-max-zipmap-entries 512 443 | hash-max-zipmap-value 64 444 | 445 | # Similarly to hashes, small lists are also encoded in a special way in order 446 | # to save a lot of space. The special representation is only used when 447 | # you are under the following limits: 448 | list-max-ziplist-entries 512 449 | list-max-ziplist-value 64 450 | 451 | # Sets have a special encoding in just one case: when a set is composed 452 | # of just strings that happens to be integers in radix 10 in the range 453 | # of 64 bit signed integers. 454 | # The following configuration setting sets the limit in the size of the 455 | # set in order to use this special memory saving encoding. 456 | set-max-intset-entries 512 457 | 458 | # Similarly to hashes and lists, sorted sets are also specially encoded in 459 | # order to save a lot of space. This encoding is only used when the length and 460 | # elements of a sorted set are below the following limits: 461 | zset-max-ziplist-entries 128 462 | zset-max-ziplist-value 64 463 | 464 | # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in 465 | # order to help rehashing the main Redis hash table (the one mapping top-level 466 | # keys to values). The hash table implementation redis uses (see dict.c) 467 | # performs a lazy rehashing: the more operation you run into an hash table 468 | # that is rhashing, the more rehashing "steps" are performed, so if the 469 | # server is idle the rehashing is never complete and some more memory is used 470 | # by the hash table. 471 | # 472 | # The default is to use this millisecond 10 times every second in order to 473 | # active rehashing the main dictionaries, freeing memory when possible. 474 | # 475 | # If unsure: 476 | # use "activerehashing no" if you have hard latency requirements and it is 477 | # not a good thing in your environment that Redis can reply form time to time 478 | # to queries with 2 milliseconds delay. 479 | # 480 | # use "activerehashing yes" if you don't have such hard requirements but 481 | # want to free memory asap when possible. 482 | activerehashing yes 483 | 484 | ################################## INCLUDES ################################### 485 | 486 | # Include one or more other config files here. This is useful if you 487 | # have a standard template that goes to all redis server but also need 488 | # to customize a few per-server settings. Include files can include 489 | # other files, so use this wisely. 490 | # 491 | # include /path/to/local.conf 492 | # include /path/to/other.conf 493 | -------------------------------------------------------------------------------- /tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | circusd circus.ini & 4 | circusctl add --start bot node ../bot.js -c configs/bot.json 5 | sleep 3 6 | nodeunit tests.js 7 | circusctl quit 8 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | var irc = require('irc'); 2 | var redis = require('redis') 3 | 4 | var botName = 'WraithTest'; 5 | var bot = null; 6 | function getRedisClient() { 7 | var client = redis.createClient('46379'); 8 | client.subscribe('in'); 9 | return client 10 | } 11 | exports.protocol = { 12 | setUp: function(callback) { 13 | this.redis = getRedisClient(); 14 | if (!bot) { 15 | bot = new irc.Client('localhost', 16 | botName, 17 | { 18 | 'port': 46667, 19 | 'channels': ['#pdxbots', '##pdxbots'] 20 | }); 21 | bot.addListener('join', function(channel, nick) { 22 | if (channel == '##pdxbots' && nick == botName){ 23 | callback(); 24 | } 25 | }) 26 | bot.addListener('error', function(message) { 27 | console.log(message); 28 | }); 29 | } else { 30 | setTimeout(function() { 31 | callback(); 32 | }, 500); 33 | } 34 | }, 35 | tearDown: function(callback) { 36 | this.redis.quit(); 37 | callback(); 38 | }, 39 | testUnofficialChannel: function(test) { 40 | test.expect(6); 41 | var msgSent = 'ohai'; 42 | var testChannel = '##pdxbots'; 43 | var timer = setTimeout(function() { 44 | console.log('timeout'); 45 | test.done(); 46 | }, 1000); 47 | this.redis.on('message', function(channel, message) { 48 | msg = JSON.parse(message); 49 | validate_message(test, msg, testChannel, msgSent); 50 | clearTimeout(timer); 51 | test.done(); 52 | }); 53 | bot.say(testChannel, msgSent); 54 | }, 55 | testOfficialChannel: function(test) { 56 | test.expect(6); 57 | var msgSent = 'ohai'; 58 | var testChannel = '#pdxbots'; 59 | var timer = setTimeout(function() { 60 | console.log('timeout'); 61 | test.done(); 62 | }, 1000); 63 | this.redis.on('message', function(channel, message) { 64 | msg = JSON.parse(message); 65 | validate_message(test, msg, testChannel, msgSent); 66 | clearTimeout(timer); 67 | test.done(); 68 | }); 69 | bot.say(testChannel, msgSent); 70 | }, 71 | testPM: function(test) { 72 | test.expect(6); 73 | var msgSent = 'ohai'; 74 | var timer = setTimeout(function() { 75 | console.log('timeout'); 76 | test.done(); 77 | }, 1000); 78 | this.redis.on('message', function(channel, message) { 79 | msg = JSON.parse(message); 80 | validate_message(test, msg, botName, msgSent); 81 | clearTimeout(timer); 82 | test.done(); 83 | }); 84 | bot.say('ZenIRCBot', msgSent); 85 | }, 86 | // testJoin: function(test) { 87 | // test.expect(1); 88 | // test.ok(false); 89 | // test.done(); 90 | // }, 91 | // testPart: function(test) { 92 | // test.expect(1); 93 | // test.ok(false); 94 | // test.done(); 95 | // }, 96 | // testQuit: function(test) { 97 | // test.expect(1); 98 | // test.ok(false); 99 | // test.done(); 100 | // } 101 | }; 102 | 103 | function validate_message(test, message, testChannel, msgSent) { 104 | test.equal(message.version, 1); 105 | test.equal(message.type, 'privmsg'); 106 | test.ok(message.data); 107 | test.equal(message.data.message, msgSent); 108 | test.equal(message.data.sender, botName); 109 | test.equal(message.data.channel, testChannel); 110 | } 111 | -------------------------------------------------------------------------------- /tests/var/lib/redis/.git-folder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenirc/zenircbot/bd9974f6dc27716a6aebd51cac882fc3a326b7b6/tests/var/lib/redis/.git-folder -------------------------------------------------------------------------------- /tests/var/log/.git-folder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenirc/zenircbot/bd9974f6dc27716a6aebd51cac882fc3a326b7b6/tests/var/log/.git-folder -------------------------------------------------------------------------------- /tests/var/run/.git-folder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenirc/zenircbot/bd9974f6dc27716a6aebd51cac882fc3a326b7b6/tests/var/run/.git-folder --------------------------------------------------------------------------------