source: trunk/amazonbot/amazonbot.py @ 17

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

debug

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