source: trunk/amazonbot/amazonbot.py @ 13

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

commentation

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, args) メ゜ッドを䜜るこずでコマンド远加可胜
83        コマンド曞匏は !HOGEHOGE arg [, arg2, ...] ずなる
84        """
85        def __init__(self):
86                _server = [(config.get('irc', 'server'), config.get('irc', 'port', 'int'))]
87                _nick = config.get('bot', 'nick')
88
89                self._prev_time = time.time()
90                self._silent = False
91                SingleServerIRCBot.__init__(self, _server, _nick, _nick)
92
93        def start(self):
94                try:
95                        SingleServerIRCBot.start(self)
96                except KeyboardInterrupt:
97                        self.die(ununicoding(config.get('bot', 'bye')))
98
99        def on_welcome(self, c, e):
100                c.join(config.get('irc', 'channel'))
101                if __debug__:
102                        print >> sys.stderr, 'DEBUG> Joined %s' % config.get('irc', 'channel')
103
104        def on_nicknameinuse(self, c, e):
105                c.nick(c.get_nickname() + '_')
106
107        def on_privmsg(self, c, e):
108                return self.on_pubmsg(c, e)
109
110        def on_pubmsg(self, c, e):
111                msg = unicoding(e.arguments()[0])
112                if msg[0] == '!':
113                        words = shlex.split(ununicoding(msg, 'utf-8')[1:])
114                        method = getattr(self, 'onmsg_%s' % words[0], lambda *arg: False)
115                        return method(c, e, words[1:]) # words[0] == command name
116
117                _current_time = time.time()
118                if _current_time < self._prev_time + config.get('bot', 'freq', 'int'):
119                        if __debug__:
120                                prev = time.strftime('%y/%m/%d %H:%M:%S', time.localtime(self._prev_time))
121                                print >> sys.stderr, 'DEBUG> Not expired: prev time is %s' % prev
122                        return False
123                self._prev_time = _current_time
124
125                self.silence(msg, c, e)
126                if self._silent:
127                        return False
128
129                nominals = mecab_parse(msg)
130                if not nominals:
131                        if __debug__:
132                                print >> sys.stderr, "DEBUG> Couldn't find nominal words"
133                        return False
134
135                title, url = self.process_keyword(' '.join(nominals))
136                if title and url:
137                        channel = e.target()
138                        content = unicoding(config.get('bot', 'content'))
139                        try:
140                                message = ununicoding(': '.join([content, title, url]))
141                        except UnicodeError, err:
142                                # なぜかたたに unicode オブゞェクトを iso-2022-jp で゚ンコヌドできない
143                                if __debug__:
144                                        print >> sys.stderr, 'DEBUG> %s' % str(err)
145                                return False
146
147                        c.privmsg(channel, message)
148                        return True
149                return False
150
151        ACTIVE_PATTERN = re.compile(unicoding(config.get('bot', 'active_pattern')))
152        SILENT_PATTERN = re.compile(unicoding(config.get('bot', 'silent_pattern')))
153        def silence(self, msg, c, e):
154                ch = e.target()
155                active = self.ACTIVE_PATTERN.search(msg)
156                silent = self.SILENT_PATTERN.search(msg)
157                if __debug__:
158                        print >> sys.stderr, 'DEBUG> ACT_PATT: %s, SIL_PATT: %s' % (str(active), str(silent))
159
160                if active:
161                        self._silent = False
162                        c.privmsg(ch, ununicoding(config.get('bot', 'thanks')))
163                elif silent:
164                        self._silent = True
165                        c.privmsg(ch, ununicoding(config.get('bot', 'sorry')))
166
167        def process_keyword(self, keyword):
168                return [None, None]
169
170class AmazonBot(AmazonBotBase):
171        """アマゟンボットの実装クラス
172        process_keyword メ゜ッドで Amazon ぞク゚リを投げお結果を返す
173        """
174        def __init__(self):
175                AmazonBotBase.__init__(self)
176
177        def get_version(self):
178                return 'AmazonBot by %s, based on python-irclib' % __author__
179
180        def onmsg_isbn(self, c, e, args):
181                """Syntax: !isbn <ISBN number>
182                """
183                return self.onmsg_asin(c, e, args)
184        def onmsg_asin(self, c, e, args): # FIXME: arg がなかったずきの゚ラヌ凊理を远加するこず
185                """Syntax: !asin <ASIN number>
186                """
187                if __debug__:
188                        print >> sys.stderr, 'DEBUG> in asin command: %s' % str(args)
189
190                try:
191                        data = my_amazon.searchByASIN(args[0])
192                except my_amazon.AmazonError, err:
193                        ch = e.target()
194                        c.privmsg(ch, ununicoding(config.get('bot', 'no_products')))
195                        if __debug__:
196                                print >> sys.stderr, 'DEBUG> Caught AmazonError in onmsg_asin: %s' % str(err)
197                        return False
198
199                return self._process_onmsg(c, e, data)
200
201        def onmsg_a(self, c, e, args):
202                """Syntax: !a [-h] [-t type] keyword1 [, keyword2, ...]
203                """
204                return self.onmsg_amazon(c, e, args)
205        def onmsg_amazon(self, c, e, args): # FIXME: arg がなかったずきの゚ラヌ凊理を远加するこず
206                """Syntax: !amazon [-h] [-t type] keyword1 [, keyword2, ...]
207                """
208                if __debug__:
209                        print >> sys.stderr, 'DEBUG> in amazon command: %s' % str(args)
210
211                try:
212                        options, rest = getopt.getopt(args, 't:h', ['type=', 'help'])
213                except getopt.GetoptError, err:
214                        if __debug__:
215                                print >> sys.stderr, 'DEBUG> Caught GetoptError in onmsg_amazon: %s' % str(err)
216                        return False
217
218                keyword = ' '.join(rest)
219                product_line = 'books-jp'
220                for opt, val in options:
221                        if opt in ['-t', '--type']:
222                                product_line = val
223                                break
224                        elif opt in ['-h', '--help']:
225                                available = [
226                                        'books-jp (和曞, default)', 'books-us (掋曞)',
227                                        'music-jp (ポピュラヌ音楜)', 'classical-jp (クラシック音楜)',
228                                        'dvd-jp (DVD)', 'vhs-jp (ビデオ)',
229                                        'electronics-jp (゚レクトロニクス)', 'kitchen-jp (ホヌムキッチン)',
230                                        'software-jp (゜フトりェア)', 'videogames-jp (ゲヌム)',
231                                        'magazines-jp (雑誌)', 'toys-jp (おもちゃホビヌ)',
232                                        ]
233                                ch = e.target()
234                                c.privmsg(ch, ununicoding('Available types: ' + ', '.join(available)))
235                                return True
236
237                if __debug__:
238                        fmt = 'DEBUG> keyword="%s", product_line=%s'
239                        print >> sys.stderr, fmt % (ununicoding(keyword, 'euc-jp'), product_line)
240
241                try:
242                        data = my_amazon.searchByKeyword(keyword, product_line=product_line)
243                except my_amazon.AmazonError, err:
244                        ch = e.target()
245                        c.privmsg(ch, ununicoding(config.get('bot', 'no_products')))
246                        if __debug__:
247                                print >> sys.stderr, 'DEBUG> Caught AmazonError in onmsg_amazon: %s' % str(err)
248                        return False
249
250                return self._process_onmsg(c, e, data)
251
252        def onmsg_help(self, c, e, data):
253                pass
254
255        def _process_onmsg(self, c, e, data):
256                if type(data.Details) is not list:
257                        data.Details = [data.Details]
258
259                detail = random.choice(data.Details)
260                title = ununicoding(detail.ProductName)
261                url = ununicoding(detail.URL)
262                channel = config.get('irc', 'channel')
263                c.privmsg(channel, '%(title)s: %(url)s' % locals())
264
265                return True
266
267        def process_keyword(self, keyword):
268                keyword = ununicoding(keyword, 'utf-8')
269                if __debug__:
270                        print >> sys.stderr, 'DEBUG> KEYWORD: %s' % ununicoding(keyword, 'euc-jp')
271
272                try:
273                        data = my_amazon.searchByBlended(keyword)
274                        if type(data.ProductLine) is not type([]):
275                                data.ProductLine = [data.ProductLine]
276                except my_amazon.AmazonError, err:
277                        if __debug__:
278                                print >> sys.stderr, 'DEBUG> Caught AmazonError: %s' % str(err)
279                        return [None, None]
280
281                product_line = random.choice(data.ProductLine)
282                detail = random.choice(product_line.ProductInfo.Details)
283
284                url = unicoding(getattr(detail, 'URL', None))
285                product_name = unicoding(getattr(detail, 'ProductName', None))
286
287                return [product_name, url]
288
289if __name__ == '__main__':
290        bot = AmazonBot()
291        bot.start()
Note: See TracBrowser for help on using the repository browser.