source: trunk/amazonbot/amazonbot.py @ 18

Revision 18, 10.8 KB checked in by atzm, 18 years ago (diff)

add count comments

RevLine 
[8]1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4__version__ = '$Revision$'
5__author__ = 'Atzm WATANABE <sitosito@p.chan.ne.jp>'
6__date__ = '$Date$'
7__copyright__ = 'Copyright(C) 2006 Atzm WATANABE, all rights reserved.'
8__license__ = 'Python'
9
10import re
[9]11import sys
[10]12import time
[12]13import shlex
[8]14import random
[12]15import getopt
16
[8]17import MeCab
[10]18import nkf
[8]19
20from ircbot import SingleServerIRCBot
21from irclib import nm_to_n
22
23import config
24config.init()
25
26import my_amazon
27my_amazon.setLocale(config.get('amazon', 'locale'))
28my_amazon.setLicense(config.get('amazon', 'access_key'))
29
30try:
31        set, frozenset
32except NameError:
33        from sets import Set as set, ImmutableSet as frozenset
34
35def uniq(sequence):
[10]36        """リストから重耇を取り陀く (順番が狂うので泚意)
37        """
[8]38        return list(set(sequence))
39
[10]40def unicoding(text):
41        """text を匷制的に unicode オブゞェクトに倉換
42        """
43        if type(text) is unicode:
44                return text
45        return unicode(nkf.nkf('-w', text), 'utf-8')
46
47def ununicoding(text, encoding='iso-2022-jp'):
48        """text を指定された encoding で゚ンコヌドしraw str に匷制倉換
49        """
50        if type(text) is not unicode:
51                return unicoding(text).encode(encoding)
52        return text.encode(encoding)
53
[8]54def mecab_parse(text):
[10]55        """MeCab を䜿っお圢態玠解析し固有名詞ず䞀般名詞だけを抜出する
56        """
[8]57        def choice_nominal(wlist):
58                res = []
59                for word, wtype in wlist:
60                        wtypes = wtype.split('-')
61                        if '固有名詞' in wtypes or ('名詞' in wtypes and '䞀般' in wtypes):
[10]62                                res.append(unicoding(word))
[8]63                return res
64
[10]65        text = ununicoding(text, 'utf-8')
[8]66        result = []
67        tag = MeCab.Tagger('-Ochasen')
68        for line in tag.parse(text).split('\n'):
69                if not line or line == 'EOS':
70                        break
71                words = line.split()
72                result.append((words[0], words[-1])) # word, word-type
73
74        result = uniq(choice_nominal(result))
75        return result
76
77class AmazonBotBase(SingleServerIRCBot):
[10]78        """アマゟンボットのベヌスクラス
[12]79        単䜓では受け取ったメッセヌゞの圢態玠解析ず名詞抜出たでしかやらない
[10]80        サブクラスで process_keyword を実装しお Amazon ぞク゚リを投げるべし
[12]81
[14]82        サブクラスには onmsg_HOGEHOGE(self, conn, ev, to, args) メ゜ッドを䜜るこずでコマンド远加可胜
[12]83        コマンド曞匏は !HOGEHOGE arg [, arg2, ...] ずなる
[14]84        ヘルプはメ゜ッドに docstring を曞けば OK
[10]85        """
[8]86        def __init__(self):
87                _server = [(config.get('irc', 'server'), config.get('irc', 'port', 'int'))]
88                _nick = config.get('bot', 'nick')
89
[18]90                self._current_lines = 0
91                self._prev_time = time.time() - config.get('freq', 'timeout', 'int')
[8]92                self._silent = False
93                SingleServerIRCBot.__init__(self, _server, _nick, _nick)
94
[10]95        def start(self):
96                try:
97                        SingleServerIRCBot.start(self)
98                except KeyboardInterrupt:
99                        self.die(ununicoding(config.get('bot', 'bye')))
[8]100
101        def on_welcome(self, c, e):
102                c.join(config.get('irc', 'channel'))
103                if __debug__:
[10]104                        print >> sys.stderr, 'DEBUG> Joined %s' % config.get('irc', 'channel')
[8]105
106        def on_nicknameinuse(self, c, e):
107                c.nick(c.get_nickname() + '_')
108
109        def on_privmsg(self, c, e):
[14]110                return self.on_pubmsg(c, e, to=nm_to_n(e.source()))
[8]111
[14]112        def on_pubmsg(self, c, e, to=config.get('irc', 'channel')):
[12]113                msg = unicoding(e.arguments()[0])
[14]114
115                if __debug__:
[17]116                        try:
117                                print >> sys.stderr, 'DEBUG> pubmsg incoming "%s", should be reply to %s' % (ununicoding(msg, 'euc-jp'), to)
118                        except:
119                                print >> sys.stderr, 'DEBUG> pubmsg incoming, but could not en/decode message. should be reply to %s' % to
[14]120
[12]121                if msg[0] == '!':
[17]122                        try:
123                                words = shlex.split(ununicoding(msg, 'utf-8')[1:])
124                        except:
125                                return False
[16]126                        if not words:
127                                return False
[12]128                        method = getattr(self, 'onmsg_%s' % words[0], lambda *arg: False)
[14]129                        return method(c, e, to, words[1:]) # words[0] == command name
[12]130
[18]131                # freq_lines
132                self._current_lines += 1
133                _freq_lines = config.get('freq', 'lines', 'int')
134                if _freq_lines:
135                        if config.get('freq', 'lines_random', 'boolean'):
136                                _freq_lines = random.randint(int(_freq_lines/2)+1, _freq_lines)
137
138                        if __debug__:
139                                print >> sys.stderr, 'DEBUG> Line count: now %d, next: %d' % (self._current_lines, _freq_lines)
140
141                        if self._current_lines < _freq_lines:
142                                return False
143                self._current_lines = 0
144
145                # freq
[11]146                _current_time = time.time()
[18]147                if _current_time < self._prev_time + config.get('freq', 'timeout', 'int'):
[9]148                        if __debug__:
[15]149                                cur = time.strftime('%H:%M:%S', time.localtime(_current_time))
[18]150                                go = time.strftime('%H:%M:%S', time.localtime(self._prev_time + config.get('freq', 'timeout', 'int')))
[15]151                                print >> sys.stderr, 'DEBUG> Not expired: now %s, be expired at: %s' % (cur, go)
[9]152                        return False
[11]153                self._prev_time = _current_time
[10]154
[18]155                # silence
[14]156                self.silence(msg, c, e, to)
[8]157                if self._silent:
158                        return False
159
[10]160                nominals = mecab_parse(msg)
[8]161                if not nominals:
[10]162                        if __debug__:
163                                print >> sys.stderr, "DEBUG> Couldn't find nominal words"
[8]164                        return False
165
166                title, url = self.process_keyword(' '.join(nominals))
167                if title and url:
[10]168                        content = unicoding(config.get('bot', 'content'))
[9]169                        try:
[10]170                                message = ununicoding(': '.join([content, title, url]))
[12]171                        except UnicodeError, err:
[11]172                                # なぜかたたに unicode オブゞェクトを iso-2022-jp で゚ンコヌドできない
173                                if __debug__:
[12]174                                        print >> sys.stderr, 'DEBUG> %s' % str(err)
[9]175                                return False
176
[14]177                        c.notice(to, message)
[8]178                        return True
179                return False
180
[10]181        ACTIVE_PATTERN = re.compile(unicoding(config.get('bot', 'active_pattern')))
182        SILENT_PATTERN = re.compile(unicoding(config.get('bot', 'silent_pattern')))
[14]183        def silence(self, msg, c, e, to):
[10]184                active = self.ACTIVE_PATTERN.search(msg)
185                silent = self.SILENT_PATTERN.search(msg)
[9]186                if __debug__:
[10]187                        print >> sys.stderr, 'DEBUG> ACT_PATT: %s, SIL_PATT: %s' % (str(active), str(silent))
[9]188
189                if active:
[8]190                        self._silent = False
[14]191                        c.notice(to, ununicoding(config.get('bot', 'thanks')))
[9]192                elif silent:
[8]193                        self._silent = True
[14]194                        c.notice(to, ununicoding(config.get('bot', 'sorry')))
[8]195
196        def process_keyword(self, keyword):
197                return [None, None]
198
199class AmazonBot(AmazonBotBase):
[10]200        """アマゟンボットの実装クラス
201        process_keyword メ゜ッドで Amazon ぞク゚リを投げお結果を返す
202        """
[14]203        _AVAIL_PRODUCT_LINES = {
204                'books-jp': '(和曞, default)',
205                'books-us': '(掋曞)',
206                'music-jp': '(ポピュラヌ音楜)',
207                'classical-jp': '(クラシック音楜)',
208                'dvd-jp': '(DVD)',
209                'vhs-jp': '(ビデオ)',
210                'electronics-jp': '(゚レクトロニクス)',
211                'kitchen-jp': '(ホヌムキッチン)',
212                'software-jp': '(゜フトりェア)',
213                'videogames-jp': '(ゲヌム)',
214                'magazines-jp': '(雑誌)',
215                'toys-jp': '(おもちゃホビヌ)',
216        }
217
[8]218        def __init__(self):
219                AmazonBotBase.__init__(self)
220
[10]221        def get_version(self):
222                return 'AmazonBot by %s, based on python-irclib' % __author__
223
[14]224        def onmsg_isbn(self, c, e, to, args):
[12]225                """Syntax: !isbn <ISBN number>
226                """
[14]227                return self.onmsg_asin(c, e, to, args)
228        def onmsg_asin(self, c, e, to, args):
[12]229                """Syntax: !asin <ASIN number>
230                """
231                if __debug__:
232                        print >> sys.stderr, 'DEBUG> in asin command: %s' % str(args)
233
234                try:
235                        data = my_amazon.searchByASIN(args[0])
236                except my_amazon.AmazonError, err:
[14]237                        c.notice(to, ununicoding(config.get('bot', 'no_products')))
[12]238                        if __debug__:
239                                print >> sys.stderr, 'DEBUG> Caught AmazonError in onmsg_asin: %s' % str(err)
240                        return False
[14]241                except IndexError, err:
242                        c.notice(to, 'Please specify an argument.')
243                        return False
[12]244
[14]245                return self._process_onmsg(c, e, to, data)
[12]246
[14]247        def onmsg_k(self, c, e, to, args): return self.onmsg_keyword(c, e, to, args)
248        def onmsg_keyword(self, c, e, to, args):
249                """Syntax: !keyword [-h] [-t type] <keyword1> [, keyword2, ...]
[12]250                """
251                if __debug__:
[14]252                        print >> sys.stderr, 'DEBUG> in keyword command: %s' % str(args)
[12]253
254                try:
255                        options, rest = getopt.getopt(args, 't:h', ['type=', 'help'])
256                except getopt.GetoptError, err:
257                        if __debug__:
[14]258                                print >> sys.stderr, 'DEBUG> Caught GetoptError in onmsg_keyword: %s' % str(err)
[12]259                        return False
260
[14]261                keyword = ' '.join(rest).strip()
[12]262                product_line = 'books-jp'
263                for opt, val in options:
264                        if opt in ['-t', '--type']:
[14]265                                if val not in self._AVAIL_PRODUCT_LINES.keys():
266                                        c.notice(to, 'Type "%s" is not available.' % val)
267                                        return False
268
[12]269                                product_line = val
270                                break
[14]271
[12]272                        elif opt in ['-h', '--help']:
[14]273                                _from = nm_to_n(e.source()) # ログを流しおしたうのでヘルプは盎接送信元ぞ
274                                c.notice(_from, ununicoding('Available types:'))
275
276                                for key, val in self._AVAIL_PRODUCT_LINES.iteritems():
277                                        time.sleep(1) # XXX: 連続投皿するず匟かれるこずがあるので暫定察凊
278                                        c.notice(_from, ununicoding(' * %s: %s' % (key, val)))
279
[12]280                                return True
281
[14]282                if not keyword:
283                        c.notice(to, 'Please specify keywords.')
284                        return False
285
[12]286                if __debug__:
287                        fmt = 'DEBUG> keyword="%s", product_line=%s'
288                        print >> sys.stderr, fmt % (ununicoding(keyword, 'euc-jp'), product_line)
289
290                try:
291                        data = my_amazon.searchByKeyword(keyword, product_line=product_line)
292                except my_amazon.AmazonError, err:
[14]293                        c.notice(to, ununicoding(config.get('bot', 'no_products')))
[12]294                        if __debug__:
295                                print >> sys.stderr, 'DEBUG> Caught AmazonError in onmsg_amazon: %s' % str(err)
296                        return False
297
[14]298                return self._process_onmsg(c, e, to, data)
[12]299
[14]300        def onmsg_h(self, c, e, to, args): return self.onmsg_help(c, e, to, args)
301        def onmsg_help(self, c, e, to, args):
302                """Syntax: !help
303                """
304                if __debug__:
305                        print >> sys.stderr, 'DEBUG> in help command: %s' % str(args)
[12]306
[14]307                _from = nm_to_n(e.source()) # ログを流しおしたうのでヘルプは盎接送信元ぞ
308                docs = []
309                for key in dir(self):
310                        val = getattr(self, key, '')
311                        if __debug__:
312                                print >> sys.stderr, 'DEBUG> key=%s, val=%s' % (key, ununicoding(str(val), 'euc-jp'))
313
314                        if key[:6] != 'onmsg_':
315                                continue
316
317                        doc = val.__doc__
318                        if doc:
319                                doc = doc.strip()
320                                if not doc:
321                                        continue
322                                time.sleep(1) # XXX: 連続投皿するず匟かれるっぜいので暫定察凊
323                                c.notice(_from, doc)
324
325                return True
326
327        def _process_onmsg(self, c, e, to, data):
[12]328                if type(data.Details) is not list:
329                        data.Details = [data.Details]
330
331                detail = random.choice(data.Details)
332                title = ununicoding(detail.ProductName)
333                url = ununicoding(detail.URL)
[14]334                c.notice(to, '%(title)s: %(url)s' % locals())
[12]335
336                return True
337
[8]338        def process_keyword(self, keyword):
[10]339                keyword = ununicoding(keyword, 'utf-8')
340                if __debug__:
341                        print >> sys.stderr, 'DEBUG> KEYWORD: %s' % ununicoding(keyword, 'euc-jp')
342
[8]343                try:
344                        data = my_amazon.searchByBlended(keyword)
345                        if type(data.ProductLine) is not type([]):
346                                data.ProductLine = [data.ProductLine]
[12]347                except my_amazon.AmazonError, err:
[10]348                        if __debug__:
[12]349                                print >> sys.stderr, 'DEBUG> Caught AmazonError: %s' % str(err)
[8]350                        return [None, None]
351
352                product_line = random.choice(data.ProductLine)
353                detail = random.choice(product_line.ProductInfo.Details)
354
[10]355                url = unicoding(getattr(detail, 'URL', None))
356                product_name = unicoding(getattr(detail, 'ProductName', None))
[8]357
358                return [product_name, url]
359
360if __name__ == '__main__':
361        bot = AmazonBot()
362        bot.start()
[14]363        print '> Bye ;)'
Note: See TracBrowser for help on using the repository browser.