Skip to content
Newer
Older
100644 315 lines (251 sloc) 9.25 KB
35eccd1 start
George Buckenham authored Jan 16, 2010
1 """
2 twitterbot
3
4 A twitter IRC bot. Twitterbot connected to an IRC server and idles in
5 a channel, polling a twitter account and broadcasting all updates to
6 friends.
7
8 USAGE
9
10 twitterbot [config_file]
11
12 CONFIG_FILE
13
14 The config file is an ini-style file that must contain the following:
15
16 [twitter]
17 email: <twitter_account_email>
18 password: <twitter_account_password>
19
20 and optionally:
21 [debug]
22 debug: False
23
24 If no config file is given "twitterbot.ini" will be used by default.
25 """
26
27 BOT_VERSION = "TwitterBot 1.0 (http://mike.verdone.ca/twitter)"
28
29 IRC_BOLD = chr(0x02)
30 IRC_ITALIC = chr(0x16)
31 IRC_UNDERLINE = chr(0x1f)
32 IRC_REGULAR = chr(0x0f)
33
34 import sys
35 import time
36 from dateutil.parser import parse
37 from ConfigParser import SafeConfigParser
38 from heapq import heappop, heappush
39 import traceback
40 import os.path
41 import twitter
fe5c6f2 added more complicated error handling and queuing
George Buckenham authored Jan 17, 2010
42 from functools import partial
35eccd1 start
George Buckenham authored Jan 16, 2010
43
44 from twitter import TwitterError
45 import re
46 from htmlentitydefs import name2codepoint
47
34e4843 ResponsesDict
Boris the Brave authored Jan 17, 2010
48 from bearuser import BearUser
49
35eccd1 start
George Buckenham authored Jan 16, 2010
50
51
52
53 def htmlentitydecode(s):
54 return re.sub(
55 '&(%s);' % '|'.join(name2codepoint),
56 lambda m: unichr(name2codepoint[m.group(1)]), s)
57
58 debug_flag = False
59
60 def debug(msg):
61 # uncomment this for debug text stuff
62 if debug_flag:
63 print >> sys.stderr, msg
64 else:
65 pass
66
67
68 class SchedTask(object):
69 def __init__(self, task, delta, repeat):
70 self.task = task
71 self.delta = delta
72 self.next = time.time()
73 self.repeat = repeat
74
75 def __repr__(self):
76 return "<SchedTask %s next:%i delta:%i>" %(
fe5c6f2 added more complicated error handling and queuing
George Buckenham authored Jan 17, 2010
77 self.task, self.next, self.delta)
35eccd1 start
George Buckenham authored Jan 16, 2010
78
79 def __cmp__(self, other):
80 return cmp(self.next, other.next)
81
82 def __call__(self):
83 return self.task()
84
85 class Scheduler(object):
86 def __init__(self, tasks):
87 self.task_heap = []
88 for task in tasks:
89 heappush(self.task_heap, task)
90
91 def next_task(self):
92 debug("next_task")
93 now = time.time()
94 task = heappop(self.task_heap)
95 wait = task.next - now
96 task.next = now + task.delta
97 if task.repeat:
98 heappush(self.task_heap, task)
99 if (wait > 0):
100 time.sleep(wait)
fe5c6f2 added more complicated error handling and queuing
George Buckenham authored Jan 17, 2010
101 self.wrap_twitter_action(task)
35eccd1 start
George Buckenham authored Jan 16, 2010
102 debug("tasks: " + str(self.task_heap))
103
fe5c6f2 added more complicated error handling and queuing
George Buckenham authored Jan 17, 2010
104
105 def wrap_twitter_action(self, action):
106 try:
107 return action()
108 except Exception, e:
109 print >> sys.stderr, e
110 heappush(self.task_heap, action)
111
35eccd1 start
George Buckenham authored Jan 16, 2010
112 def add_task(self, task):
113 heappush(self.task_heap, task)
114
115 def run_forever(self):
116 while True:
117 self.next_task()
4f10d0e added bearuser class
George Buckenham authored Jan 16, 2010
118 class BearUser(object):
119 def __init__(self, user):
fe5c6f2 added more complicated error handling and queuing
George Buckenham authored Jan 17, 2010
120 self.user = user.screen_name
4f10d0e added bearuser class
George Buckenham authored Jan 16, 2010
121 self.mood = 0
122 self.last_updated = time.time()
b3b326b fixed some things, added a test thing when it starts up
George Buckenham authored Jan 17, 2010
123 def __repr__(self):
124 return "user %s has made bear have mood %d - last talked at %s" %(self.user, self.mood, self.last_updated)
4f10d0e added bearuser class
George Buckenham authored Jan 16, 2010
125
126 def changeMood(self, mood_change):
127 self.mood += mood_change
128 self.last_updated = time.time()
129
130 def createReply(self, keyword):
131 self.last_updated = time.time()
b3b326b fixed some things, added a test thing when it starts up
George Buckenham authored Jan 17, 2010
132 print >> sys.stderr , self
fe5c6f2 added more complicated error handling and queuing
George Buckenham authored Jan 17, 2010
133
134 return "hello, i am a bear"
bec8c09 Prelim Bear moods
Boris the Brave authored Jan 17, 2010
135
136
35eccd1 start
George Buckenham authored Jan 16, 2010
137 class TwitterBot(object):
138 def __init__(self, configFilename):
139 self.configFilename = configFilename
140 self.config = load_config(self.configFilename)
141 self.twitter = twitter.Api(
142 username=self.config.get('twitter', 'email'),
143 password=self.config.get('twitter', 'password'))
144
145 self.sched = Scheduler(
146 #(SchedTask(self.process_events, 1),
bec8c09 Prelim Bear moods
Boris the Brave authored Jan 17, 2010
147 (
148 SchedTask(self.check_dms, 120, True),
149 SchedTask(self.start_game_to_v21, 30, False),
150 SchedTask(self.check_replies, 30, True)),
151 SchedTask(self.check_mood, 600, True)),
152 )
35eccd1 start
George Buckenham authored Jan 16, 2010
153 # SchedTask(self.stay_joined, 120)))
154 self.lastDMsUpdate = time.gmtime()
155 self.lastRepliesUpdate = time.gmtime()
156 self.lastUpdate = time.gmtime()
157
4f10d0e added bearuser class
George Buckenham authored Jan 16, 2010
158 self.bearUserDict = {}
159
35eccd1 start
George Buckenham authored Jan 16, 2010
160 def start_game_to_v21(self):
161 self.start_game("v21", "you should really generalize this bit")
162 def start_game(self, follower, message):
b3b326b fixed some things, added a test thing when it starts up
George Buckenham authored Jan 17, 2010
163 class update:
164 sender_screen_name = "v21"
165 text = "i think youre a terrible person"
166
167 self.handle_dm(update)
35eccd1 start
George Buckenham authored Jan 16, 2010
168 try:
169 self.twitter.CreateFriendship(follower)
170 self.twitter.PostDirectMessage(follower, message)
171 #some kind of database action
172 except Exception, e:
173 print >> sys.stderr, "Exception while querying twitter:"
174 traceback.print_exc(file=sys.stderr)
175 return
bec8c09 Prelim Bear moods
Boris the Brave authored Jan 17, 2010
176
35eccd1 start
George Buckenham authored Jan 16, 2010
177
178 def check_dms(self):
4f10d0e added bearuser class
George Buckenham authored Jan 16, 2010
179 debug("In check_dms")
35eccd1 start
George Buckenham authored Jan 16, 2010
180 try:
181 updates = self.twitter.GetDirectMessages()
182 except Exception, e:
183 print >> sys.stderr, "Exception while querying twitter:"
184 traceback.print_exc(file=sys.stderr)
185 return
186
187 nextLastUpdate = self.lastDMsUpdate
188 for update in updates:
189 crt = parse(update.created_at).utctimetuple()
190 if (crt > self.lastUpdate):
191 text = (htmlentitydecode(
192 update.text.replace('\n', ' '))
193 .encode('utf-8', 'replace'))
194 self.handle_dm(update)
195
196 nextLastUpdate = crt
197 else:
198 break
199 self.lastDMsUpdate = nextLastUpdate
200
201 def check_replies(self):
202 debug("In check_replies")
203 try:
204 updates = self.twitter.GetReplies()
205 except Exception, e:
206 print >> sys.stderr, "Exception while querying twitter:"
207 traceback.print_exc(file=sys.stderr)
208 return
209
210 nextLastUpdate = self.lastRepliesUpdate
211 for update in updates:
212 crt = parse(update.created_at).utctimetuple()
213 if (crt > self.lastUpdate):
214 text = (htmlentitydecode(
215 update.text.replace('\n', ' '))
216 .encode('utf-8', 'replace'))
217 self.handle_replies(update)
218
219 nextLastUpdate = crt
220 else:
221 break
222 self.lastRepliesUpdate = nextLastUpdate
fe5c6f2 added more complicated error handling and queuing
George Buckenham authored Jan 17, 2010
223 def rand_delay(self):
224 return 120
35eccd1 start
George Buckenham authored Jan 16, 2010
225
226 def handle_dm(self, update):
4f10d0e added bearuser class
George Buckenham authored Jan 16, 2010
227 user = update.sender_screen_name
228
b3b326b fixed some things, added a test thing when it starts up
George Buckenham authored Jan 17, 2010
229 if not user in self.bearUserDict:
4f10d0e added bearuser class
George Buckenham authored Jan 16, 2010
230 self.bearUserDict[user] = BearUser(user=self.twitter.GetUser(user=user))
fe5c6f2 added more complicated error handling and queuing
George Buckenham authored Jan 17, 2010
231 self.sched.add_task(SchedTask(partial(self.twitter.CreateFriendship, user), self.rand_delay(), False))
232
233 bear_user = self.bearUserDict[user]
234 message = bear_user.createReply(update.text)
235 self.sched.add_task(SchedTask(partial(self.twitter.PostDirectMessage, user, message),self.rand_delay(), False ))
35eccd1 start
George Buckenham authored Jan 16, 2010
236
237 def handle_replies(self, update):
b3b326b fixed some things, added a test thing when it starts up
George Buckenham authored Jan 17, 2010
238 user = update.user.screen_name
4f10d0e added bearuser class
George Buckenham authored Jan 16, 2010
239
b3b326b fixed some things, added a test thing when it starts up
George Buckenham authored Jan 17, 2010
240 if not user in self.bearUserDict:
4f10d0e added bearuser class
George Buckenham authored Jan 16, 2010
241 self.bearUserDict[user] = BearUser(user=self.twitter.GetUser(user=user))
fe5c6f2 added more complicated error handling and queuing
George Buckenham authored Jan 17, 2010
242 self.sched.add_task(SchedTask(partial(self.twitter.CreateFriendship, user), self.rand_delay(), False))
243
244 bear_user = self.bearUserDict[user]
245 message = bear_user.createReply(update.text)
246
247 self.sched.add_task(SchedTask(partial(self.twitter.PostUpdate, status="@%s %s"%(update.user.screen_name, message), in_reply_to_status_id=update.id), self.rand_delay(), False))
248
bec8c09 Prelim Bear moods
Boris the Brave authored Jan 17, 2010
249 def get_current_mood(self):
250 moods = [bear_user.mood for bear_user in self.bearUserDict.values()]
251 return sum(moods)/len(moods)
252
253 def check_mood(self):
254 current_mood = self.get_current_mood()
255 current_mood = int(round(current_mood))
256 if current_mood > 0: current_mood = 0
257 if current_mood < -2: current_mood = 0
258 imgs = {
259 0: "http://personal.boristhebrave.com/permanent/10/bearangst.jpg"
260 -1: "http://personal.boristhebrave.com/permanent/10/sadbear.jpg"
261 -2: "http://personal.boristhebrave.com/permanent/10/angrybear.jpg"
262 }
263 img = imgs[current_mood]
264 #TODO
35eccd1 start
George Buckenham authored Jan 16, 2010
265
266 def run(self):
267 while True:
268 try:
269 self.sched.run_forever()
270 except KeyboardInterrupt:
271 break
272 except TwitterError, e:
273 # twitter.com is probably down because it sucks. ignore the fault and keep going
274 print >> sys.stderr, e
275 #except Exception, e:
276 # print >> sys.stderr, e
277
278 def load_config(filename):
279 defaults = dict(debug=dict(debug=False))
280 cp = SafeConfigParser(defaults)
281 cp.read((filename,))
282
283 # attempt to read these properties-- they are required
284 cp.get('twitter', 'email'),
285 cp.get('twitter', 'password')
286 try:
287 global debug_flag
288 debug_flag = cp.getboolean('debug', 'debug')
289 except: #debug is optional
290 pass
291
292 return cp
293
294 def main():
295 configFilename = "twitterbot.ini"
296 if (sys.argv[1:]):
297 configFilename = sys.argv[1]
298
299 try:
300 if not os.path.exists(configFilename):
301 raise Exception()
302 load_config(configFilename)
303 except Exception, e:
304 print >> sys.stderr, "Error while loading ini file %s" %(
305 configFilename)
306 print >> sys.stderr, e
307 print >> sys.stderr, __doc__
308 sys.exit(1)
309
310 bot = TwitterBot(configFilename)
311 return bot.run()
312
bec8c09 Prelim Bear moods
Boris the Brave authored Jan 17, 2010
313 if __name__ == "__main__":
314 main()
Something went wrong with that request. Please try again.