2 from twisted
.python
import log
4 from datetime
import datetime
5 import random
, time
, re
, urllib
10 def handleMsg(bot
, user
, channel
, msg
):
11 ignoreChannels
= [bot
.nickname
, 'AUTH']
12 if channel
not in ignoreChannels
:
13 log
.msg('handleMsg> %s/%s: %s' % (user
, channel
, msg
))
15 rest
= prefixMatch(msg
, [PREFIX
, bot
.nickname
+ ':'])
18 commands
= Commands(bot
, user
, channel
)
22 filters
= Filters(bot
, user
, channel
)
25 def prefixMatch(string
, prefixes
):
26 for prefix
in prefixes
:
27 if string
.startswith(prefix
):
28 return string
[len(prefix
):]
33 from BeautifulSoup
import BeautifulSoup
35 req
= urllib2
.Request(url
, None, {'User-agent': 'mozilla'})
36 contents
= urllib2
.urlopen(req
).read()
37 return BeautifulSoup(contents
)
41 return soup
.title
.contents
[0].encode('utf-8').strip()
43 def decorate(proxy
, original
):
44 # as decorator 'overwrites' namespace with these proxy
45 # functions, we need to make the introspecting code happy.
46 proxy
.__doc
__ = original
.__doc
__
49 def limitFreq(secs
, msg
=None):
51 Limit the frequency of execution of the function to avoid IRC spamming
55 def proxy(self
, arg
=None):
57 if lastmsg
[0] is not None:
58 diff
= now
- lastmsg
[0]
59 if diff
.seconds
< secs
:
67 return decorate(proxy
, func
)
73 Only admin listed in `nicks` can run this command. Silently fail otherwise.
76 def proxy(self
, *args
, **kwargs
):
77 if self
.user
in nicks
:
78 return func(self
, *args
, **kwargs
)
80 log
.msg('ADMIN: %s not authorized to run command %s' % (self
.user
, func
.__name
__))
82 return decorate(proxy
, func
)
85 class BotActions(object):
87 def __init__(self
, bot
):
90 def toChannel(self
, msg
):
91 self
.bot
.msg(self
.channel
, msg
)
93 def toUser(self
, msg
):
94 self
.toChannel('%s: %s' % (self
.user
, msg
))
96 def doAction(self
, action
):
97 self
.bot
.me(self
.channel
, action
)
99 def grep(self
, msg
, words
):
106 class Commands(BotActions
):
108 def __init__(self
, bot
, user
, channel
):
109 BotActions
.__init
__(self
, bot
)
111 self
.channel
= channel
114 parts
= rest
.split(None, 1)
118 cmdFunc
= getattr(self
, 'cmd_%s' % command
)
119 except AttributeError:
120 log
.msg('no such command: %s', command
)
124 def cmd_ping(self
, args
=None):
126 self
.toUser(args
and 'PONG %s' % args
or 'PONG')
130 def cmd_reload(self
, args
=None):
131 "reload plugins.py (self)"
138 self
.toUser('reloaded')
143 def cmd_help(self
, args
=None):
144 ignore
= ('help', 'PING')
145 for name
, func
, docstring
in self
:
146 if name
not in ignore
and docstring
is not None:
147 self
.toChannel('%10s - %s' % (name
, docstring
))
149 # more useful commands
151 def cmd_eval(self
, args
=None):
152 "evaluate a python expression"
154 from math
import * # make math functions available to `eval`
157 except SyntaxError, e
:
158 self
.toUser('wrong syntax')
162 self
.toChannel(result
)
165 def cmd_google(self
, search_terms
=None):
166 # search the internet
168 soup
= soupify('http://www.google.com/search?' + urllib
.urlencode({'q': search_terms
}))
169 firstLink
= soup
.find('a', attrs
={'class': 'l'})['href'].encode('utf-8').strip()
170 log
.msg('google:', firstLink
)
171 # firstLink = 'http://google.com/'
172 self
.toChannel('%s - %s' % (urlTitle(firstLink
), firstLink
))
176 def cmd_do(self
, args
):
177 # perform an action in a channel
178 # > ,do #foo is bored
179 channel
, action
= args
.split(None, 1)
180 self
.bot
.me(channel
, action
)
183 "Iterate over all commands (minus their aliases)"
185 for attr
in dir(self
):
186 if attr
.startswith('cmd_'):
187 func
= getattr(self
, attr
)
188 name
= attr
[len('cmd_'):]
190 # find and overwrite the alias.
191 # the name of an alias is shorter the name of the original version.
192 if len(cmdMap
[func
]) < len(name
):
197 for func
, name
in cmdMap
.items():
198 yield name
, func
, func
.__doc
__
201 class Filters(BotActions
):
203 def __init__(self
, bot
, user
, channel
):
204 BotActions
.__init
__(self
, bot
)
206 self
.channel
= channel
209 for attr
in dir(self
):
210 if attr
.startswith('filter_'):
211 filter = getattr(self
, attr
)
214 def respondWisely(self
, msg
, for_words
, with_responses
, one_for_every
=1):
215 random
.seed(time
.time())
216 if self
.grep(msg
, for_words
):
217 if random
.choice(range(one_for_every
)) == 0:
218 self
.toChannel(random
.choice(with_responses
))
220 def filter_obscene(self
, msg
):
221 words
= ['fuck', 'f**k', 'bitch', 'cunt', 'tits']
223 'The rate at which a person can mature is directly proportional to the embarrassment he can tolerate.',
224 'To make mistakes is human; to stumble is commonplace; to be able to laugh at yourself is maturity.',
225 'It is the province of knowledge to speak and it is the privilege of wisdom to listen.',
226 'Some folks are wise and some otherwise.',
227 'The wise man doesn\'t give the right answers, he poses the right questions.',
228 'There are many who know many things, yet are lacking in wisdom.',
229 'Wise men hear and see as little children do.',
230 'Speech both conceals and reveals the thoughts of men',
231 'Few people know how to take a walk. The qualifications are endurance, plain clothes, old shoes, an eye for nature, good humor, vast curiosity, good speech, good silence and nothing too much.',
232 'The trouble with her is that she lacks the power of conversation but not the power of speech.',
233 'Speak when you are angry - and you\'ll make the best speech you\'ll ever regret.',
234 'Speech is human, silence is divine, yet also brutish and dead: therefore we must learn both arts.',
235 'We must have reasons for speech but we need none for silence',
236 'It usually takes more than three weeks to prepare a good impromptu speech.',
237 'Men use thought only as authority for their injustice, and employ speech only to conceal their thoughts',
238 'Much talking is the cause of danger. Silence is the means of avoiding misfortune. The talkative parrot is shut up in a cage. Other birds, without speech, fly freely about.',
239 'Silence is also speech',
240 'Speak properly, and in as few words as you can, but always plainly; for the end of speech is not ostentation, but to be understood.',
243 self
.respondWisely(msg
, words
, quotes
)
245 def filter_hatred(self
, msg
):
246 words
= ['hate', 'suck', 'bad']
248 'The first reaction to truth is hatred',
249 'Hatred does not cease by hatred, but only by love; this is the eternal rule.',
250 'Hatred is settled anger',
251 'I love humanity but I hate people.',
252 'Let them hate, so long as they fear.',
253 'What we need is hatred. From it our ideas are born.',
256 self
.respondWisely(msg
, words
, quotes
, 3)
258 def filter_m8ball(self
, msg
):
259 # This list is taken from fsbot@emacs (type ',dl m8ball')
261 "Yes", "No", "Definitely", "Of course not!", "Highly likely.",
262 "Ask yourself, do you really want to know?",
263 "I'm telling you, you don't want to know.",
267 self
.respondWisely(msg
, ['??'], answers
)
269 def filter_random(self
, msg
):
270 random
.seed(time
.time())
271 if random
.choice(range(100)) == 0:
274 'looks around nervously',
277 _filter_links_url
= re
.compile('.*(https?://[^ ]*).*')
278 def filter_links(self
, msg
):
279 "Show <title> in channel"
280 match
= self
._filter
_links
_url
.match(msg
)
282 self
.toChannel('Title: %s' % urlTitle(match
.group(1)))