python-irclibを使ってIRCのBotを作成する

というわけでIRC BotPythonを使って、書いてみる。
細かい作業やらせようとすると色々と面倒になるけど、
簡単な振る舞いの実装までてけてけ書いて行こうと思う。

環境

$ uname -a  
Linux hoge 2.6.32-358.23.2.el6.x86_64 #1 SMP Wed Oct 16 18:37:12 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
$ python -V 
Python 2.6.6

インストール

PyPIにパッケージとして登録されてるけど、pipでインストールしようとすると上手くいかない。
easy_installも同様、上手くいかない

$ pip search irclib        
python-irclib             - IRC client library
irclib                    - 
Douglbutt                 - Simple, extensible irclib based IRC bot.

$ pip install python-irclib
Downloading/unpacking python-irclib
  Could not find any downloads that satisfy the requirement python-irclib
Cleaning up...
No distributions at all found for python-irclib
Storing complete log in /home/tututen/.pip/pip.log

$ pip install irclib       
Downloading/unpacking irclib
  Could not find any downloads that satisfy the requirement irclib
Cleaning up...
No distributions at all found for irclib
Storing complete log in /home/tututen/.pip/pip.log

$ easy_install irclib
Searching for python-irclib
Reading https://pypi.python.org/simple/python-irclib/
Reading http://python-irclib.sourceforge.net
Reading http://sourceforge.net/project/showfiles.php?group_id=38297
Best match: python-irclib 0.6.4
Downloading http://sourceforge.net/projects/python-irclib/files/python-irclib-0.6.4.zip/download
Processing python-irclib-0.6.4.zip
Writing /tmp/easy_install-fADYl5/python-irclib-0.6.4/setup.cfg
Running python-irclib-0.6.4/setup.py -q bdist_egg --dist-dir /tmp/easy_install-fADYl5/python-irclib-0.6.4/egg-dist-tmp-KP109_
error: paver-minilib.zip: No such file or directory


諦めて、sourceforgeのWebサイトの方へ行くと、
MercurialリポジトリがBitbucketにあるっぽい、けど作者さんのリポじゃないっぽい。
まぁ、SourceForgeから落としてきます

ちなみに
PyPI: python-irclib 0.4.8 : Python Package Index
SourceForge: Python IRC client library - Browse Files at SourceForge.net

# 現時点 0.6.4が最新版

$ wget http://downloads.sourceforge.net/project/python-irclib/python-irclib-0.6.4.zip
$ unzip python-irclib-0.6.4.zip
Archive:  python-irclib-0.6.4.zip
  inflating: python-irclib-0.6.4/.cvsignore  
  inflating: python-irclib-0.6.4/.hgignore  
  inflating: python-irclib-0.6.4/.hgtags  
  inflating: python-irclib-0.6.4/ChangeLog  
  inflating: python-irclib-0.6.4/CHANGES  
  inflating: python-irclib-0.6.4/COPYING  
  inflating: python-irclib-0.6.4/MANIFEST.in  
  inflating: python-irclib-0.6.4/pavement.py  
  inflating: python-irclib-0.6.4/paver-minilib.zip  
  inflating: python-irclib-0.6.4/PKG-INFO  
  inflating: python-irclib-0.6.4/README  
  inflating: python-irclib-0.6.4/setup.cfg  
  inflating: python-irclib-0.6.4/setup.py  
  inflating: python-irclib-0.6.4/lib/ircbot.py  
  inflating: python-irclib-0.6.4/lib/irclib.py  
  inflating: python-irclib-0.6.4/lib/python_irclib.egg-info/dependency_links.txt  
  inflating: python-irclib-0.6.4/lib/python_irclib.egg-info/PKG-INFO  
  inflating: python-irclib-0.6.4/lib/python_irclib.egg-info/SOURCES.txt  
  inflating: python-irclib-0.6.4/lib/python_irclib.egg-info/top_level.txt  
  inflating: python-irclib-0.6.4/scripts/dccreceive  
  inflating: python-irclib-0.6.4/scripts/dccsend  
  inflating: python-irclib-0.6.4/scripts/irccat  
  inflating: python-irclib-0.6.4/scripts/irccat2  
  inflating: python-irclib-0.6.4/scripts/servermap  
  inflating: python-irclib-0.6.4/scripts/testbot.py  
  inflating: python-irclib-0.6.4/tests/test_ircbot.py  
  inflating: python-irclib-0.6.4/tests/test_irclib.py  
  inflating: python-irclib-0.6.4/tests/__init__.py  
 $ cd python-irclib-0.6.4
 $ python setup.py
 Searching for hgtools
Reading https://pypi.python.org/simple/hgtools/
Best match: hgtools 4.0
Downloading https://pypi.python.org/packages/source/h/hgtools/hgtools-4.0.zip#md5=a1b58801ec06de50ce4247f66c28e9cd
Processing hgtools-4.0.zip
Writing /tmp/easy_install-VR6nzx/hgtools-4.0/setup.cfg
Running hgtools-4.0/setup.py -q bdist_egg --dist-dir /tmp/easy_install-VR6nzx/hgtools-4.0/egg-dist-tmp-kuajse
Traceback (most recent call last):
  File "setup.py", line 10, in <module>
    paver.tasks.main()
  File "paver-minilib.zip/paver/tasks.py", line 808, in main
  File "paver-minilib.zip/paver/tasks.py", line 786, in _launch_pavement
  File "paver-minilib.zip/paver/tasks.py", line 746, in _process_commands
  File "paver-minilib.zip/paver/tasks.py", line 107, in get_task
  File "paver-minilib.zip/paver/setuputils.py", line 167, in get_task
  File "paver-minilib.zip/paver/setuputils.py", line 195, in _get_distribution
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/setuptools/dist.py", line 265, in __init__
    self.fetch_build_eggs(attrs.pop('setup_requires'))
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/setuptools/dist.py", line 289, in fetch_build_eggs
    parse_requirements(requires), installer=self.fetch_build_egg
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/pkg_resources.py", line 618, in resolve
    dist = best[req.key] = env.best_match(req, self, installer)
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/pkg_resources.py", line 862, in best_match
    return self.obtain(req, installer) # try and download/install
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/pkg_resources.py", line 874, in obtain
    return installer(requirement)
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/setuptools/dist.py", line 339, in fetch_build_egg
    return cmd.easy_install(req)
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/setuptools/command/easy_install.py", line 623, in easy_install
    return self.install_item(spec, dist.location, tmpdir, deps)
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/setuptools/command/easy_install.py", line 653, in install_item
    dists = self.install_eggs(spec, download, tmpdir)
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/setuptools/command/easy_install.py", line 849, in install_eggs
    return self.build_and_install(setup_script, setup_base)
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/setuptools/command/easy_install.py", line 1130, in build_and_install
    self.run_setup(setup_script, setup_base, args)
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/setuptools/command/easy_install.py", line 1115, in run_setup
    run_setup(setup_script, args)
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/setuptools/sandbox.py", line 66, in run_setup
    working_set.__init__()
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/pkg_resources.py", line 476, in __init__
    self.add_entry(entry)
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/pkg_resources.py", line 491, in add_entry
    for dist in find_distributions(entry, True):
   File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/pkg_resources.py", line 1963, in find_in_zip
    metadata = EggMetadata(importer)
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/pkg_resources.py", line 1852, in __init__
    self.zipinfo = build_zipmanifest(importer.archive)
  File "/home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/pkg_resources.py", line 1583, in build_zipmanifest
    zfile = zipfile.ZipFile(path)
  File "/usr/lib64/python2.6/zipfile.py", line 683, in __init__
    self.fp = open(file, modeDict[mode])
IOError: [Errno 2] No such file or directory: 'paver-minilib.zip'


なんか「paver-minilib.zip」なんてねぇよ!ッて言われてます。
でも「python-irclib.0.6.4」の中にちゃんと含まれてます。
そこで絶対パスの指定に変更しちゃいます。

  • setup.py
try:                                                                               
    import paver.tasks                                                             
except ImportError:                                                                
    import os                                                                      
    if os.path.exists("paver-minilib.zip"):                                        
        import sys                                                                 
        sys.path.insert(0, "paver-minilib.zip")
        import paver.tasks                                                             
                                                                               
paver.tasks.main() 

の中身の

sys.path.insert(0, "paver-minilib.zip")

を、os.path.abspathを使って

sys.path.insert(0, os.path.abspath("paver-minilib.zip"))

に変更して実行すると、

$ python setup.py
Searching for hgtools
Reading https://pypi.python.org/simple/hgtools/
Best match: hgtools 4.0
Downloading https://pypi.python.org/packages/source/h/hgtools/hgtools-4.0.zip#md5=a1b58801ec06de50ce4247f66c28e9cd
Processing hgtools-4.0.zip
Writing /tmp/easy_install-xMEenz/hgtools-4.0/setup.cfg
Running hgtools-4.0/setup.py -q bdist_egg --dist-dir /tmp/easy_install-xMEenz/hgtools-4.0/egg-dist-tmp-mIOSpg
running bdist_egg
running egg_info
writing hgtools.egg-info/PKG-INFO
writing top-level names to hgtools.egg-info/top_level.txt
writing dependency_links to hgtools.egg-info/dependency_links.txt
writing entry points to hgtools.egg-info/entry_points.txt
writing hgtools.egg-info/PKG-INFO
writing top-level names to hgtools.egg-info/top_level.txt
writing dependency_links to hgtools.egg-info/dependency_links.txt
writing entry points to hgtools.egg-info/entry_points.txt
reading manifest file 'hgtools.egg-info/SOURCES.txt'
writing manifest file 'hgtools.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-x86_64/egg
running install_lib
running build_py
creating build
creating build/lib
creating build/lib/hgtools
copying hgtools/versioning.py -> build/lib/hgtools
copying hgtools/__init__.py -> build/lib/hgtools
copying hgtools/plugins.py -> build/lib/hgtools
creating build/lib/hgtools/tests
copying hgtools/tests/test_managers.py -> build/lib/hgtools/tests
copying hgtools/tests/test_versioning.py -> build/lib/hgtools/tests
copying hgtools/tests/__init__.py -> build/lib/hgtools/tests
copying hgtools/tests/test_reentry.py -> build/lib/hgtools/tests
creating build/lib/hgtools/managers
copying hgtools/managers/subprocess.py -> build/lib/hgtools/managers
copying hgtools/managers/reentry.py -> build/lib/hgtools/managers
copying hgtools/managers/__init__.py -> build/lib/hgtools/managers
copying hgtools/managers/library.py -> build/lib/hgtools/managers
copying hgtools/managers/base.py -> build/lib/hgtools/managers
copying hgtools/managers/cmd.py -> build/lib/hgtools/managers
creating build/bdist.linux-x86_64
creating build/bdist.linux-x86_64/egg
creating build/bdist.linux-x86_64/egg/hgtools
creating build/bdist.linux-x86_64/egg/hgtools/tests
copying build/lib/hgtools/tests/test_managers.py -> build/bdist.linux-x86_64/egg/hgtools/tests
copying build/lib/hgtools/tests/test_versioning.py -> build/bdist.linux-x86_64/egg/hgtools/tests
copying build/lib/hgtools/tests/__init__.py -> build/bdist.linux-x86_64/egg/hgtools/tests
copying build/lib/hgtools/tests/test_reentry.py -> build/bdist.linux-x86_64/egg/hgtools/tests
copying build/lib/hgtools/versioning.py -> build/bdist.linux-x86_64/egg/hgtools
creating build/bdist.linux-x86_64/egg/hgtools/managers
copying build/lib/hgtools/managers/subprocess.py -> build/bdist.linux-x86_64/egg/hgtools/managers
copying build/lib/hgtools/managers/reentry.py -> build/bdist.linux-x86_64/egg/hgtools/managers
copying build/lib/hgtools/managers/__init__.py -> build/bdist.linux-x86_64/egg/hgtools/managers
copying build/lib/hgtools/managers/library.py -> build/bdist.linux-x86_64/egg/hgtools/managers
copying build/lib/hgtools/managers/base.py -> build/bdist.linux-x86_64/egg/hgtools/managers
copying build/lib/hgtools/managers/cmd.py -> build/bdist.linux-x86_64/egg/hgtools/managers
copying build/lib/hgtools/__init__.py -> build/bdist.linux-x86_64/egg/hgtools
copying build/lib/hgtools/plugins.py -> build/bdist.linux-x86_64/egg/hgtools
byte-compiling build/bdist.linux-x86_64/egg/hgtools/tests/test_managers.py to test_managers.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/tests/test_versioning.py to test_versioning.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/tests/__init__.py to __init__.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/tests/test_reentry.py to test_reentry.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/versioning.py to versioning.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/managers/subprocess.py to subprocess.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/managers/reentry.py to reentry.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/managers/__init__.py to __init__.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/managers/library.py to library.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/managers/base.py to base.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/managers/cmd.py to cmd.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/__init__.py to __init__.pyc
byte-compiling build/bdist.linux-x86_64/egg/hgtools/plugins.py to plugins.pyc
creating build/bdist.linux-x86_64/egg/EGG-INFO
copying hgtools.egg-info/PKG-INFO -> build/bdist.linux-x86_64/egg/EGG-INFO
copying hgtools.egg-info/SOURCES.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying hgtools.egg-info/dependency_links.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying hgtools.egg-info/entry_points.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying hgtools.egg-info/top_level.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
zip_safe flag not set; analyzing archive contents...
creating '/tmp/easy_install-xMEenz/hgtools-4.0/egg-dist-tmp-mIOSpg/hgtools-4.0-py2.6.egg' and adding 'build/bdist.linux-x86_64/egg' to it
removing 'build/bdist.linux-x86_64/egg' (and everything under it)
Moving hgtools-4.0-py2.6.egg to /home/tututen/work/ircbot/python-irclib-0.6.4

Installed /home/tututen/work/ircbot/python-irclib-0.6.4/hgtools-4.0-py2.6.egg


上手くBuild通ったので、インストールしちゃいましょう。

$ python setup.py install
running install
running build
running build_py
creating build
creating build/lib
copying lib/irclib.py -> build/lib
copying lib/ircbot.py -> build/lib
running install_lib
copying build/lib/ircbot.py -> /home/tututen/_sandbox/ircbot/lib/python2.6/site-packages
copying build/lib/irclib.py -> /home/tututen/_sandbox/ircbot/lib/python2.6/site-packages
byte-compiling /home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/ircbot.py to ircbot.pyc
byte-compiling /home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/irclib.py to irclib.pyc
running install_egg_info
running egg_info
writing lib/python_irclib.egg-info/PKG-INFO
writing top-level names to lib/python_irclib.egg-info/top_level.txt
writing dependency_links to lib/python_irclib.egg-info/dependency_links.txt
reading manifest file 'lib/python_irclib.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'lib/python_irclib.egg-info/SOURCES.txt'
Copying lib/python_irclib.egg-info to /home/tututen/_sandbox/ircbot/lib/python2.6/site-packages/python_irclib-0.6.4-py2.6.egg-info
running install_scripts


上手く出来たっぽいです。
確認の為にimportしてみましょう。

$ python
Python 2.6.6 (r266:84292, Nov 22 2013, 12:16:22) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import irclib
>>> 


大丈夫そうですね。

サンプル

動作確認まで行けたのでコードを書いて行きます。
ちょっと長いですが、最低限の機能ですので。

# coding: utf-8

from irclib import nm_to_n, nm_to_h
from ircbot import SingleServerIRCBot

IRC_SERVER = 'irc.ircnet.ne.jp'
IRC_PORT = 6667
IRC_PASS = ''
IRC_NICK = 'test_bot'
CHANNEL = '#tututen_my'
ENCODING = 'iso-2022-jp'


class IRCBot(SingleServerIRCBot):
    def __init__(self):
        print 'init'
        if IRC_PASS:
            SingleServerIRCBot.__init__(self,
                                        [(IRC_SERVER, IRC_PORT, IRC_PASS)],
                                        IRC_NICK,
                                        IRC_NICK)
        else:
            SingleServerIRCBot.__init__(self,
                                        [(IRC_SERVER, IRC_PORT)],
                                        IRC_NICK,
                                        IRC_NICK)

    def say(self, irc, msg):
        u"""
        文字コードの変換をして発言する
        """
        if type(msg) != 'unicode':
            msg = unicode(msg)

        irc.privmsg(CHANNEL, msg.encode(ENCODING, 'replace'))


    def on_nicknameinuse(self, irc, e):
        u"""
        BOTの名前がかぶったら名前変えるよ
        """
        print 'on_nicknameinuse'
        irc.nick(irc.get_nickname() + '_')


    def on_welcome(self, irc, e):
        u"""
        IRCサーバ接続時の処理するよ
        """
        print 'on_welcome'


        # チャンネルに入る+挨拶
        irc.join(CHANNEL)
        self.say(irc, u'こんにちは')


    def on_join(self, irc, e):
        u"""
        誰かがチャンネルに繋いだ時に処理するよ(自分含)
        """
        print 'on_join'

        # 接続した名前
        nick = nm_to_n(e.source())

        if nick == irc.get_nickname():
            return

        # 今繋いでるチャンネルの情報を取得する
        ch = self.channels[e.target()]

        # Botになるとあったら繋いだ人になると上げるよ
        if ch.is_oper(irc.ircname):
            irc.mode(e.target(), '+o %s' % nick)


    def on_privmsg(self, irc, e):
        u"""
        誰かが発言した時に処理するよ
        """
        print 'on_privmsg'

        nick, host, msg = '', '', ''
        try:
            nick = nm_to_n(e.source())
            host = nm_to_h(e.source())
            msg = unicode(e.arguments()[0], ENCODING)
        except Exception, e:
            print str(e)
            return


        if msg[0] == ':':
            command = msg[1:]

            # :omikuji って発言すると占ってくれるよ
            if command == 'omikuji':
                omikuji_list = [u'大吉', u'中吉', u'吉', u'凶']
                import random
                omikuji_msg = u'%s さんの運勢は %sです' % \
                                (nick, random.choice(omikuji_list))
                self.say(irc, omikuji_msg)


    # pubmsgでも反応するようにする
    on_pubmsg = on_privmsg


if __name__ == '__main__':
    IRCBot().start()
起動方法
$ python bot.py
説明

on_welcomeはサーバ接続時に、
on_joinはチャンネル接続時、
on_nicknameinuseはニックネームが被った時、
on_privmsgはチャンネルの発言時にイベントが発生します。


それぞれを利用して、以下の機能を実装してます。

  • サーバ接続時に指定したチャンネルに「こんにちは」と発言
  • チャンネル接続時、BOTになるとがあったらなると付与
  • 「:omikuji」 って発言するとおみくじ結果出力


ごくごく簡単な例ですが、参考に慣れば幸いです。