~ubuntu-branches/ubuntu/utopic/gozerbot/utopic

« back to all changes in this revision

Viewing changes to gplugs/identi.py

  • Committer: Package Import Robot
  • Author(s): Jeremy Malcolm
  • Date: 2011-10-15 11:54:00 UTC
  • mfrom: (1.1.6) (3.1.10 sid)
  • Revision ID: package-import@ubuntu.com-20111015115400-zbya66kh6r3tsa0a
Tags: 0.99.1-1
* New upstream version (Closes: #630359, #640850)
* Fixed initscript to better test for RUNUSER emptyness (Closes: #612434)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# gozerplugs/identi.py
 
2
 
3
# based on the Twitter post plugin
 
4
# (c) Wijnand 'maze' Modderman <http://tehmaze.com>
 
5
# BSD License
 
6
#
 
7
# I included the Twitter API, because I had to fix some minor issues. These
 
8
# issues have been reported so maybe the API part will be removed from this
 
9
# plugin at a later stadium.
 
10
 
 
11
# IMPORT SECTION
 
12
 
 
13
import os
 
14
from gozerbot.commands import cmnds
 
15
from gozerbot.datadir import datadir
 
16
from gozerbot.examples import examples
 
17
from gozerbot.persist.pdol import Pdol
 
18
from gozerbot.plughelp import plughelp
 
19
from gozerbot.utils.textutils import html_unescape
 
20
from gozerbot.utils.generic import waitforqueue
 
21
from gozerbot.plughelp import plughelp
 
22
from gozerbot.tests import tests
 
23
from gozerbot.users import users
 
24
 
 
25
# END IMPORT
 
26
 
 
27
plughelp.add('identi', 'do the identi.ca')
 
28
 
 
29
__version__ = '0.2'
 
30
 
 
31
#
 
32
# twitter-py stuff
 
33
#
 
34
# Twitter API
 
35
# (c) DeWitt Clinton <dewitt@google.com>
 
36
# Apache License 2.0
 
37
 
 
38
import base64
 
39
import hashlib
 
40
import os
 
41
import sys
 
42
import tempfile
 
43
import time
 
44
import urllib
 
45
import urllib2
 
46
import urlparse
 
47
import simplejson
 
48
 
 
49
class TwitterError(Exception):
 
50
  '''Base class for Twitter errors'''
 
51
 
 
52
class Status(object):
 
53
  '''A class representing the Status structure used by the twitter API.
 
54
 
 
55
  The Status structure exposes the following properties:
 
56
 
 
57
    status.created_at
 
58
    status.created_at_in_seconds # read only
 
59
    status.id
 
60
    status.text
 
61
    status.relative_created_at # read only
 
62
    status.user
 
63
 
 
64
 
 
65
  '''
 
66
  def __init__(self,
 
67
               created_at=None,
 
68
               id=None,
 
69
               text=None,
 
70
               user=None,
 
71
               now=None):
 
72
    '''An object to hold a Twitter status message.
 
73
 
 
74
    This class is normally instantiated by the twitter.Api class and
 
75
    returned in a sequence.
 
76
 
 
77
    Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007"
 
78
 
 
79
    Args:
 
80
      created_at: The time this status message was posted
 
81
      id: The unique id of this status message
 
82
      text: The text of this status message
 
83
      relative_created_at:
 
84
        A human readable string representing the posting time
 
85
      user:
 
86
        A twitter.User instance representing the person posting the message
 
87
      now:
 
88
        The current time, if the client choses to set it.  Defaults to the
 
89
        wall clock time.
 
90
 
 
91
 
 
92
    '''
 
93
    self.created_at = created_at
 
94
    self.id = id
 
95
    self.text = text
 
96
    self.user = user
 
97
    self.now = now
 
98
 
 
99
  def GetCreatedAt(self):
 
100
    '''Get the time this status message was posted.
 
101
 
 
102
    Returns:
 
103
      The time this status message was posted
 
104
    '''
 
105
    return self._created_at
 
106
 
 
107
  def SetCreatedAt(self, created_at):
 
108
    '''Set the time this status message was posted.
 
109
 
 
110
    Args:
 
111
      created_at: The time this status message was created
 
112
    '''
 
113
    self._created_at = created_at
 
114
 
 
115
  created_at = property(GetCreatedAt, SetCreatedAt,
 
116
                        doc='The time this status message was posted.')
 
117
 
 
118
  def GetCreatedAtInSeconds(self):
 
119
    '''Get the time this status message was posted, in seconds since the epoch.
 
120
 
 
121
    Returns:
 
122
      The time this status message was posted, in seconds since the epoch.
 
123
    '''
 
124
    return time.mktime(time.strptime(self.created_at, '%a %b %d %H:%M:%S +0000 %Y'))
 
125
 
 
126
  created_at_in_seconds = property(GetCreatedAtInSeconds,
 
127
                                   doc="The time this status message was "
 
128
                                       "posted, in seconds since the epoch")
 
129
 
 
130
  def GetId(self):
 
131
    '''Get the unique id of this status message.
 
132
 
 
133
    Returns:
 
134
      The unique id of this status message
 
135
    '''
 
136
    return self._id
 
137
 
 
138
  def SetId(self, id):
 
139
    '''Set the unique id of this status message.
 
140
 
 
141
    Args:
 
142
      id: The unique id of this status message
 
143
    '''
 
144
    self._id = id
 
145
 
 
146
  id = property(GetId, SetId,
 
147
                doc='The unique id of this status message.')
 
148
 
 
149
  def GetText(self):
 
150
    '''Get the text of this status message.
 
151
 
 
152
    Returns:
 
153
      The text of this status message.
 
154
    '''
 
155
    return self._text
 
156
 
 
157
  def SetText(self, text):
 
158
    '''Set the text of this status message.
 
159
 
 
160
    Args:
 
161
      text: The text of this status message
 
162
    '''
 
163
    self._text = text
 
164
 
 
165
  text = property(GetText, SetText,
 
166
                  doc='The text of this status message')
 
167
 
 
168
  def GetRelativeCreatedAt(self):
 
169
    '''Get a human redable string representing the posting time
 
170
 
 
171
    Returns:
 
172
      A human readable string representing the posting time
 
173
    '''
 
174
    fudge = 1.25
 
175
    delta  = int(self.now) - int(self.created_at_in_seconds)
 
176
 
 
177
    if delta < (1 * fudge):
 
178
      return 'about a second ago'
 
179
    elif delta < (60 * (1/fudge)):
 
180
      return 'about %d seconds ago' % (delta)
 
181
    elif delta < (60 * fudge):
 
182
      return 'about a minute ago'
 
183
    elif delta < (60 * 60 * (1/fudge)):
 
184
      return 'about %d minutes ago' % (delta / 60)
 
185
    elif delta < (60 * 60 * fudge):
 
186
      return 'about an hour ago'
 
187
    elif delta < (60 * 60 * 24 * (1/fudge)):
 
188
      return 'about %d hours ago' % (delta / (60 * 60))
 
189
    elif delta < (60 * 60 * 24 * fudge):
 
190
      return 'about a day ago'
 
191
    else:
 
192
      return 'about %d days ago' % (delta / (60 * 60 * 24))
 
193
 
 
194
  relative_created_at = property(GetRelativeCreatedAt,
 
195
                                 doc='Get a human readable string representing'
 
196
                                     'the posting time')
 
197
 
 
198
  def GetUser(self):
 
199
    '''Get a twitter.User reprenting the entity posting this status message.
 
200
 
 
201
    Returns:
 
202
      A twitter.User reprenting the entity posting this status message
 
203
    '''
 
204
    return self._user
 
205
 
 
206
  def SetUser(self, user):
 
207
    '''Set a twitter.User reprenting the entity posting this status message.
 
208
 
 
209
    Args:
 
210
      user: A twitter.User reprenting the entity posting this status message
 
211
    '''
 
212
    self._user = user
 
213
 
 
214
  user = property(GetUser, SetUser,
 
215
                  doc='A twitter.User reprenting the entity posting this '
 
216
                      'status message')
 
217
 
 
218
  def GetNow(self):
 
219
    '''Get the wallclock time for this status message.
 
220
 
 
221
    Used to calculate relative_created_at.  Defaults to the time
 
222
    the object was instantiated.
 
223
 
 
224
    Returns:
 
225
      Whatever the status instance believes the current time to be,
 
226
      in seconds since the epoch.
 
227
    '''
 
228
    if self._now is None:
 
229
      self._now = time.mktime(time.gmtime())
 
230
    return self._now
 
231
 
 
232
  def SetNow(self, now):
 
233
    '''Set the wallclock time for this status message.
 
234
 
 
235
    Used to calculate relative_created_at.  Defaults to the time
 
236
    the object was instantiated.
 
237
 
 
238
    Args:
 
239
      now: The wallclock time for this instance.
 
240
    '''
 
241
    self._now = now
 
242
 
 
243
  now = property(GetNow, SetNow,
 
244
                 doc='The wallclock time for this status instance.')
 
245
 
 
246
 
 
247
  def __ne__(self, other):
 
248
    return not self.__eq__(other)
 
249
 
 
250
  def __eq__(self, other):
 
251
    try:
 
252
      return other and \
 
253
             self.created_at == other.created_at and \
 
254
             self.id == other.id and \
 
255
             self.text == other.text and \
 
256
             self.user == other.user
 
257
    except AttributeError:
 
258
      return False
 
259
 
 
260
  def __str__(self):
 
261
    '''A string representation of this twitter.Status instance.
 
262
 
 
263
    The return value is the same as the JSON string representation.
 
264
 
 
265
    Returns:
 
266
      A string representation of this twitter.Status instance.
 
267
    '''
 
268
    return self.AsJsonString()
 
269
 
 
270
  def AsJsonString(self):
 
271
    '''A JSON string representation of this twitter.Status instance.
 
272
 
 
273
    Returns:
 
274
      A JSON string representation of this twitter.Status instance
 
275
   '''
 
276
    return simplejson.dumps(self.AsDict(), sort_keys=True)
 
277
 
 
278
  def AsDict(self):
 
279
    '''A dict representation of this twitter.Status instance.
 
280
 
 
281
    The return value uses the same key names as the JSON representation.
 
282
 
 
283
    Return:
 
284
      A dict representing this twitter.Status instance
 
285
    '''
 
286
    data = {}
 
287
    if self.created_at:
 
288
      data['created_at'] = self.created_at
 
289
    if self.id:
 
290
      data['id'] = self.id
 
291
    if self.text:
 
292
      data['text'] = self.text
 
293
    if self.user:
 
294
      data['user'] = self.user.AsDict()
 
295
    return data
 
296
 
 
297
  @staticmethod
 
298
  def NewFromJsonDict(data):
 
299
    '''Create a new instance based on a JSON dict.
 
300
 
 
301
    Args:
 
302
      data: A JSON dict, as converted from the JSON in the twitter API
 
303
    Returns:
 
304
      A twitter.Status instance
 
305
    '''
 
306
    if 'user' in data:
 
307
      user = User.NewFromJsonDict(data['user'])
 
308
    else:
 
309
      user = None
 
310
    return Status(created_at=data.get('created_at', None),
 
311
                  id=data.get('id', None),
 
312
                  text=data.get('text', None),
 
313
                  user=user)
 
314
 
 
315
 
 
316
class User(object):
 
317
  '''A class representing the User structure used by the twitter API.
 
318
 
 
319
  The User structure exposes the following properties:
 
320
 
 
321
    user.id
 
322
    user.name
 
323
    user.screen_name
 
324
    user.location
 
325
    user.description
 
326
    user.profile_image_url
 
327
    user.url
 
328
    user.status
 
329
  '''
 
330
  def __init__(self,
 
331
               id=None,
 
332
               name=None,
 
333
               screen_name=None,
 
334
               location=None,
 
335
               description=None,
 
336
               profile_image_url=None,
 
337
               url=None,
 
338
               status=None):
 
339
    self.id = id
 
340
    self.name = name
 
341
    self.screen_name = screen_name
 
342
    self.location = location
 
343
    self.description = description
 
344
    self.profile_image_url = profile_image_url
 
345
    self.url = url
 
346
    self.status = status
 
347
 
 
348
 
 
349
  def GetId(self):
 
350
    '''Get the unique id of this user.
 
351
 
 
352
    Returns:
 
353
      The unique id of this user
 
354
    '''
 
355
    return self._id
 
356
 
 
357
  def SetId(self, id):
 
358
    '''Set the unique id of this user.
 
359
 
 
360
    Args:
 
361
      id: The unique id of this user.
 
362
    '''
 
363
    self._id = id
 
364
 
 
365
  id = property(GetId, SetId,
 
366
                doc='The unique id of this user.')
 
367
 
 
368
  def GetName(self):
 
369
    '''Get the real name of this user.
 
370
 
 
371
    Returns:
 
372
      The real name of this user
 
373
    '''
 
374
    return self._name
 
375
 
 
376
  def SetName(self, name):
 
377
    '''Set the real name of this user.
 
378
 
 
379
    Args:
 
380
      name: The real name of this user
 
381
    '''
 
382
    self._name = name
 
383
 
 
384
  name = property(GetName, SetName,
 
385
                  doc='The real name of this user.')
 
386
 
 
387
  def GetScreenName(self):
 
388
    '''Get the short username of this user.
 
389
 
 
390
    Returns:
 
391
      The short username of this user
 
392
    '''
 
393
    return self._screen_name
 
394
 
 
395
  def SetScreenName(self, screen_name):
 
396
    '''Set the short username of this user.
 
397
 
 
398
    Args:
 
399
      screen_name: the short username of this user
 
400
    '''
 
401
    self._screen_name = screen_name
 
402
 
 
403
  screen_name = property(GetScreenName, SetScreenName,
 
404
                         doc='The short username of this user.')
 
405
 
 
406
  def GetLocation(self):
 
407
    '''Get the geographic location of this user.
 
408
 
 
409
    Returns:
 
410
      The geographic location of this user
 
411
    '''
 
412
    return self._location
 
413
 
 
414
  def SetLocation(self, location):
 
415
    '''Set the geographic location of this user.
 
416
 
 
417
    Args:
 
418
      location: The geographic location of this user
 
419
    '''
 
420
    self._location = location
 
421
 
 
422
  location = property(GetLocation, SetLocation,
 
423
                      doc='The geographic location of this user.')
 
424
 
 
425
  def GetDescription(self):
 
426
    '''Get the short text description of this user.
 
427
 
 
428
    Returns:
 
429
      The short text description of this user
 
430
    '''
 
431
    return self._description
 
432
 
 
433
  def SetDescription(self, description):
 
434
    '''Set the short text description of this user.
 
435
 
 
436
    Args:
 
437
      description: The short text description of this user
 
438
    '''
 
439
    self._description = description
 
440
 
 
441
  description = property(GetDescription, SetDescription,
 
442
                         doc='The short text description of this user.')
 
443
 
 
444
  def GetUrl(self):
 
445
    '''Get the homepage url of this user.
 
446
 
 
447
    Returns:
 
448
      The homepage url of this user
 
449
    '''
 
450
    return self._url
 
451
 
 
452
  def SetUrl(self, url):
 
453
    '''Set the homepage url of this user.
 
454
 
 
455
    Args:
 
456
      url: The homepage url of this user
 
457
    '''
 
458
    self._url = url
 
459
 
 
460
  url = property(GetUrl, SetUrl,
 
461
                 doc='The homepage url of this user.')
 
462
 
 
463
  def GetProfileImageUrl(self):
 
464
    '''Get the url of the thumbnail of this user.
 
465
 
 
466
    Returns:
 
467
      The url of the thumbnail of this user
 
468
    '''
 
469
    return self._profile_image_url
 
470
 
 
471
  def SetProfileImageUrl(self, profile_image_url):
 
472
    '''Set the url of the thumbnail of this user.
 
473
 
 
474
    Args:
 
475
      profile_image_url: The url of the thumbnail of this user
 
476
    '''
 
477
    self._profile_image_url = profile_image_url
 
478
 
 
479
  profile_image_url= property(GetProfileImageUrl, SetProfileImageUrl,
 
480
                              doc='The url of the thumbnail of this user.')
 
481
 
 
482
  def GetStatus(self):
 
483
    '''Get the latest twitter.Status of this user.
 
484
 
 
485
    Returns:
 
486
      The latest twitter.Status of this user
 
487
    '''
 
488
    return self._status
 
489
 
 
490
  def SetStatus(self, status):
 
491
    '''Set the latest twitter.Status of this user.
 
492
 
 
493
    Args:
 
494
      status: The latest twitter.Status of this user
 
495
    '''
 
496
    self._status = status
 
497
 
 
498
  status = property(GetStatus, SetStatus,
 
499
                  doc='The latest twitter.Status of this user.')
 
500
 
 
501
  def __ne__(self, other):
 
502
    return not self.__eq__(other)
 
503
 
 
504
  def __eq__(self, other):
 
505
    try:
 
506
      return other and \
 
507
             self.id == other.id and \
 
508
             self.name == other.name and \
 
509
             self.screen_name == other.screen_name and \
 
510
             self.location == other.location and \
 
511
             self.description == other.description and \
 
512
             self.profile_image_url == other.profile_image_url and \
 
513
             self.url == other.url and \
 
514
             self.status == other.status
 
515
    except AttributeError:
 
516
      return False
 
517
 
 
518
  def __str__(self):
 
519
    '''A string representation of this twitter.User instance.
 
520
 
 
521
    The return value is the same as the JSON string representation.
 
522
 
 
523
    Returns:
 
524
      A string representation of this twitter.User instance.
 
525
    '''
 
526
    return self.AsJsonString()
 
527
 
 
528
  def AsJsonString(self):
 
529
    '''A JSON string representation of this twitter.User instance.
 
530
 
 
531
    Returns:
 
532
      A JSON string representation of this twitter.User instance
 
533
   '''
 
534
    return simplejson.dumps(self.AsDict(), sort_keys=True)
 
535
 
 
536
  def AsDict(self):
 
537
    '''A dict representation of this twitter.User instance.
 
538
 
 
539
    The return value uses the same key names as the JSON representation.
 
540
 
 
541
    Return:
 
542
      A dict representing this twitter.User instance
 
543
    '''
 
544
    data = {}
 
545
    if self.id:
 
546
      data['id'] = self.id
 
547
    if self.name:
 
548
      data['name'] = self.name
 
549
    if self.screen_name:
 
550
      data['screen_name'] = self.screen_name
 
551
    if self.location:
 
552
      data['location'] = self.location
 
553
    if self.description:
 
554
      data['description'] = self.description
 
555
    if self.profile_image_url:
 
556
      data['profile_image_url'] = self.profile_image_url
 
557
    if self.url:
 
558
      data['url'] = self.url
 
559
    if self.status:
 
560
      data['status'] = self.status.AsDict()
 
561
    return data
 
562
 
 
563
  @staticmethod
 
564
  def NewFromJsonDict(data):
 
565
    '''Create a new instance based on a JSON dict.
 
566
 
 
567
    Args:
 
568
      data: A JSON dict, as converted from the JSON in the twitter API
 
569
    Returns:
 
570
      A twitter.User instance
 
571
    '''
 
572
    if 'status' in data:
 
573
      status = Status.NewFromJsonDict(data['status'])
 
574
    else:
 
575
      status = None
 
576
    return User(id=data.get('id', None),
 
577
                name=data.get('name', None),
 
578
                screen_name=data.get('screen_name', None),
 
579
                location=data.get('location', None),
 
580
                description=data.get('description', None),
 
581
                profile_image_url=data.get('profile_image_url', None),
 
582
                url=data.get('url', None),
 
583
                status=status)
 
584
 
 
585
class DirectMessage(object):
 
586
  '''A class representing the DirectMessage structure used by the twitter API.
 
587
 
 
588
  The DirectMessage structure exposes the following properties:
 
589
 
 
590
    direct_message.id
 
591
    direct_message.created_at
 
592
    direct_message.created_at_in_seconds # read only
 
593
    direct_message.sender_id
 
594
    direct_message.sender_screen_name
 
595
    direct_message.recipient_id
 
596
    direct_message.recipient_screen_name
 
597
    direct_message.text
 
598
  '''
 
599
 
 
600
  def __init__(self,
 
601
               id=None,
 
602
               created_at=None,
 
603
               sender_id=None,
 
604
               sender_screen_name=None,
 
605
               recipient_id=None,
 
606
               recipient_screen_name=None,
 
607
               text=None):
 
608
    '''An object to hold a Twitter direct message.
 
609
 
 
610
    This class is normally instantiated by the twitter.Api class and
 
611
    returned in a sequence.
 
612
 
 
613
    Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007"
 
614
 
 
615
    Args:
 
616
      id: The unique id of this direct message
 
617
      created_at: The time this direct message was posted
 
618
      sender_id: The id of the twitter user that sent this message
 
619
      sender_screen_name: The name of the twitter user that sent this message
 
620
      recipient_id: The id of the twitter that received this message
 
621
      recipient_screen_name: The name of the twitter that received this message
 
622
      text: The text of this direct message
 
623
    '''
 
624
    self.id = id
 
625
    self.created_at = created_at
 
626
    self.sender_id = sender_id
 
627
    self.sender_screen_name = sender_screen_name
 
628
    self.recipient_id = recipient_id
 
629
    self.recipient_screen_name = recipient_screen_name
 
630
    self.text = text
 
631
 
 
632
  def GetId(self):
 
633
    '''Get the unique id of this direct message.
 
634
 
 
635
    Returns:
 
636
      The unique id of this direct message
 
637
    '''
 
638
    return self._id
 
639
 
 
640
  def SetId(self, id):
 
641
    '''Set the unique id of this direct message.
 
642
 
 
643
    Args:
 
644
      id: The unique id of this direct message
 
645
    '''
 
646
    self._id = id
 
647
 
 
648
  id = property(GetId, SetId,
 
649
                doc='The unique id of this direct message.')
 
650
 
 
651
  def GetCreatedAt(self):
 
652
    '''Get the time this direct message was posted.
 
653
 
 
654
    Returns:
 
655
      The time this direct message was posted
 
656
    '''
 
657
    return self._created_at
 
658
 
 
659
  def SetCreatedAt(self, created_at):
 
660
    '''Set the time this direct message was posted.
 
661
 
 
662
    Args:
 
663
      created_at: The time this direct message was created
 
664
    '''
 
665
    self._created_at = created_at
 
666
 
 
667
  created_at = property(GetCreatedAt, SetCreatedAt,
 
668
                        doc='The time this direct message was posted.')
 
669
 
 
670
  def GetCreatedAtInSeconds(self):
 
671
    '''Get the time this direct message was posted, in seconds since the epoch.
 
672
 
 
673
    Returns:
 
674
      The time this direct message was posted, in seconds since the epoch.
 
675
    '''
 
676
    return time.mktime(time.strptime(self.created_at, '%a %b %d %H:%M:%S +0000 %Y'))
 
677
 
 
678
  created_at_in_seconds = property(GetCreatedAtInSeconds,
 
679
                                   doc="The time this direct message was "
 
680
                                       "posted, in seconds since the epoch")
 
681
 
 
682
  def GetSenderId(self):
 
683
    '''Get the unique sender id of this direct message.
 
684
 
 
685
    Returns:
 
686
      The unique sender id of this direct message
 
687
    '''
 
688
    return self._sender_id
 
689
 
 
690
  def SetSenderId(self, sender_id):
 
691
    '''Set the unique sender id of this direct message.
 
692
 
 
693
    Args:
 
694
      sender id: The unique sender id of this direct message
 
695
    '''
 
696
    self._sender_id = sender_id
 
697
 
 
698
  sender_id = property(GetSenderId, SetSenderId,
 
699
                doc='The unique sender id of this direct message.')
 
700
 
 
701
  def GetSenderScreenName(self):
 
702
    '''Get the unique sender screen name of this direct message.
 
703
 
 
704
    Returns:
 
705
      The unique sender screen name of this direct message
 
706
    '''
 
707
    return self._sender_screen_name
 
708
 
 
709
  def SetSenderScreenName(self, sender_screen_name):
 
710
    '''Set the unique sender screen name of this direct message.
 
711
 
 
712
    Args:
 
713
      sender_screen_name: The unique sender screen name of this direct message
 
714
    '''
 
715
    self._sender_screen_name = sender_screen_name
 
716
 
 
717
  sender_screen_name = property(GetSenderScreenName, SetSenderScreenName,
 
718
                doc='The unique sender screen name of this direct message.')
 
719
 
 
720
  def GetRecipientId(self):
 
721
    '''Get the unique recipient id of this direct message.
 
722
 
 
723
    Returns:
 
724
      The unique recipient id of this direct message
 
725
    '''
 
726
    return self._recipient_id
 
727
 
 
728
  def SetRecipientId(self, recipient_id):
 
729
    '''Set the unique recipient id of this direct message.
 
730
 
 
731
    Args:
 
732
      recipient id: The unique recipient id of this direct message
 
733
    '''
 
734
    self._recipient_id = recipient_id
 
735
 
 
736
  recipient_id = property(GetRecipientId, SetRecipientId,
 
737
                doc='The unique recipient id of this direct message.')
 
738
 
 
739
  def GetRecipientScreenName(self):
 
740
    '''Get the unique recipient screen name of this direct message.
 
741
 
 
742
    Returns:
 
743
      The unique recipient screen name of this direct message
 
744
    '''
 
745
    return self._recipient_screen_name
 
746
 
 
747
  def SetRecipientScreenName(self, recipient_screen_name):
 
748
    '''Set the unique recipient screen name of this direct message.
 
749
 
 
750
    Args:
 
751
      recipient_screen_name: The unique recipient screen name of this direct message
 
752
    '''
 
753
    self._recipient_screen_name = recipient_screen_name
 
754
 
 
755
  recipient_screen_name = property(GetRecipientScreenName, SetRecipientScreenName,
 
756
                doc='The unique recipient screen name of this direct message.')
 
757
 
 
758
  def GetText(self):
 
759
    '''Get the text of this direct message.
 
760
 
 
761
    Returns:
 
762
      The text of this direct message.
 
763
    '''
 
764
    return self._text
 
765
 
 
766
  def SetText(self, text):
 
767
    '''Set the text of this direct message.
 
768
 
 
769
    Args:
 
770
      text: The text of this direct message
 
771
    '''
 
772
    self._text = text
 
773
 
 
774
  text = property(GetText, SetText,
 
775
                  doc='The text of this direct message')
 
776
 
 
777
  def __ne__(self, other):
 
778
    return not self.__eq__(other)
 
779
 
 
780
  def __eq__(self, other):
 
781
    try:
 
782
      return other and \
 
783
          self.id == other.id and \
 
784
          self.created_at == other.created_at and \
 
785
          self.sender_id == other.sender_id and \
 
786
          self.sender_screen_name == other.sender_screen_name and \
 
787
          self.recipient_id == other.recipient_id and \
 
788
          self.recipient_screen_name == other.recipient_screen_name and \
 
789
          self.text == other.text
 
790
    except AttributeError:
 
791
      return False
 
792
 
 
793
  def __str__(self):
 
794
    '''A string representation of this twitter.DirectMessage instance.
 
795
 
 
796
    The return value is the same as the JSON string representation.
 
797
 
 
798
    Returns:
 
799
      A string representation of this twitter.DirectMessage instance.
 
800
    '''
 
801
    return self.AsJsonString()
 
802
 
 
803
  def AsJsonString(self):
 
804
    '''A JSON string representation of this twitter.DirectMessage instance.
 
805
 
 
806
    Returns:
 
807
      A JSON string representation of this twitter.DirectMessage instance
 
808
   '''
 
809
    return simplejson.dumps(self.AsDict(), sort_keys=True)
 
810
 
 
811
  def AsDict(self):
 
812
    '''A dict representation of this twitter.DirectMessage instance.
 
813
 
 
814
    The return value uses the same key names as the JSON representation.
 
815
 
 
816
    Return:
 
817
      A dict representing this twitter.DirectMessage instance
 
818
    '''
 
819
    data = {}
 
820
    if self.id:
 
821
      data['id'] = self.id
 
822
    if self.created_at:
 
823
      data['created_at'] = self.created_at
 
824
    if self.sender_id:
 
825
      data['sender_id'] = self.sender_id
 
826
    if self.sender_screen_name:
 
827
      data['sender_screen_name'] = self.sender_screen_name
 
828
    if self.recipient_id:
 
829
      data['recipient_id'] = self.recipient_id
 
830
    if self.recipient_screen_name:
 
831
      data['recipient_screen_name'] = self.recipient_screen_name
 
832
    if self.text:
 
833
      data['text'] = self.text
 
834
    return data
 
835
 
 
836
  @staticmethod
 
837
  def NewFromJsonDict(data):
 
838
    '''Create a new instance based on a JSON dict.
 
839
 
 
840
    Args:
 
841
      data: A JSON dict, as converted from the JSON in the twitter API
 
842
    Returns:
 
843
      A twitter.DirectMessage instance
 
844
    '''
 
845
    return DirectMessage(created_at=data.get('created_at', None),
 
846
                         recipient_id=data.get('recipient_id', None),
 
847
                         sender_id=data.get('sender_id', None),
 
848
                         text=data.get('text', None),
 
849
                         sender_screen_name=data.get('sender_screen_name', None),
 
850
                         id=data.get('id', None),
 
851
                         recipient_screen_name=data.get('recipient_screen_name', None))
 
852
 
 
853
class Api(object):
 
854
  '''A python interface into the Twitter API
 
855
 
 
856
  By default, the Api caches results for 1 minute.
 
857
 
 
858
  Example usage:
 
859
 
 
860
    To create an instance of the twitter.Api class, with no authentication:
 
861
 
 
862
      >>> import twitter
 
863
      >>> api = twitter.Api()
 
864
 
 
865
    To fetch the most recently posted public twitter status messages:
 
866
 
 
867
      >>> statuses = api.GetPublicTimeline()
 
868
      >>> print [s.user.name for s in statuses]
 
869
      [u'DeWitt', u'Kesuke Miyagi', u'ev', u'Buzz Andersen', u'Biz Stone'] #...
 
870
 
 
871
    To fetch a single user's public status messages, where "user" is either
 
872
    a Twitter "short name" or their user id.
 
873
 
 
874
      >>> statuses = api.GetUserTimeline(user)
 
875
      >>> print [s.text for s in statuses]
 
876
 
 
877
    To use authentication, instantiate the twitter.Api class with a
 
878
    username and password:
 
879
 
 
880
      >>> api = twitter.Api(username='twitter user', password='twitter pass')
 
881
 
 
882
    To fetch your friends (after being authenticated):
 
883
 
 
884
      >>> users = api.GetFriends()
 
885
      >>> print [u.name for u in users]
 
886
 
 
887
    To post a twitter status message (after being authenticated):
 
888
 
 
889
      >>> status = api.PostUpdate('I love python-twitter!')
 
890
      >>> print status.text
 
891
      I love python-twitter!
 
892
 
 
893
    There are many other methods, including:
 
894
 
 
895
      >>> api.PostDirectMessage(user, text)
 
896
      >>> api.GetUser(user)
 
897
      >>> api.GetReplies()
 
898
      >>> api.GetUserTimeline(user)
 
899
      >>> api.GetStatus(id)
 
900
      >>> api.DestroyStatus(id)
 
901
      >>> api.GetFriendsTimeline(user)
 
902
      >>> api.GetFriends(user)
 
903
      >>> api.GetFollowers()
 
904
      >>> api.GetFeatured()
 
905
      >>> api.GetDirectMessages()
 
906
      >>> api.PostDirectMessage(user, text)
 
907
      >>> api.DestroyDirectMessage(id)
 
908
      >>> api.DestroyFriendship(user)
 
909
      >>> api.CreateFriendship(user)
 
910
  '''
 
911
 
 
912
  DEFAULT_CACHE_TIMEOUT = 60 # cache for 1 minute
 
913
 
 
914
  _API_REALM = 'Twitter API'
 
915
 
 
916
  def __init__(self,
 
917
               username=None,
 
918
               password=None,
 
919
               input_encoding=None,
 
920
               request_headers=None):
 
921
    '''Instantiate a new twitter.Api object.
 
922
 
 
923
    Args:
 
924
      username: The username of the twitter account.  [optional]
 
925
      password: The password for the twitter account. [optional]
 
926
      input_encoding: The encoding used to encode input strings. [optional]
 
927
      request_header: A dictionary of additional HTTP request headers. [optional]
 
928
 
 
929
 
 
930
    '''
 
931
    self._cache = _FileCache()
 
932
    self._urllib = urllib2
 
933
    self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT
 
934
    self._InitializeRequestHeaders(request_headers)
 
935
    self._InitializeUserAgent()
 
936
    self._input_encoding = input_encoding
 
937
    self.SetCredentials(username, password)
 
938
 
 
939
  def GetPublicTimeline(self, since_id=None):
 
940
    '''Fetch the sequnce of public twitter.Status message for all users.
 
941
 
 
942
    Args:
 
943
      since_id:
 
944
        Returns only public statuses with an ID greater than (that is,
 
945
        more recent than) the specified ID. [Optional]
 
946
 
 
947
    Returns:
 
948
      An sequence of twitter.Status instances, one for each message
 
949
 
 
950
 
 
951
    '''
 
952
    parameters = {}
 
953
    if since_id:
 
954
      parameters['since_id'] = since_id
 
955
    url = 'https://identi.ca/api/statuses/public_timeline.json'
 
956
    json = self._FetchUrl(url,  parameters=parameters)
 
957
    data = simplejson.loads(json)
 
958
    return [Status.NewFromJsonDict(x) for x in data]
 
959
 
 
960
  def GetFriendsTimeline(self, user=None, since=None):
 
961
    '''Fetch the sequence of twitter.Status messages for a user's friends
 
962
 
 
963
    The twitter.Api instance must be authenticated if the user is private.
 
964
 
 
965
    Args:
 
966
      user:
 
967
        Specifies the ID or screen name of the user for whom to return
 
968
        the friends_timeline.  If unspecified, the username and password
 
969
        must be set in the twitter.Api instance.  [optional]
 
970
      since:
 
971
        Narrows the returned results to just those statuses created
 
972
        after the specified HTTP-formatted date. [optional]
 
973
 
 
974
    Returns:
 
975
      A sequence of twitter.Status instances, one for each message
 
976
 
 
977
 
 
978
    '''
 
979
    if user:
 
980
      url = 'https://identi.ca/api/statuses/friends_timeline/%s.json' % user
 
981
    elif not user and not self._username:
 
982
      raise TwitterError("User must be specified if API is not authenticated.")
 
983
    else:
 
984
      url = 'https://identi.ca/api/statuses/friends_timeline.json'
 
985
    parameters = {}
 
986
    if since:
 
987
      parameters['since'] = since
 
988
    json = self._FetchUrl(url, parameters=parameters)
 
989
    data = simplejson.loads(json)
 
990
    return [Status.NewFromJsonDict(x) for x in data]
 
991
 
 
992
  def GetUserTimeline(self, user=None, count=None, since=None):
 
993
    '''Fetch the sequence of public twitter.Status messages for a single user.
 
994
 
 
995
    The twitter.Api instance must be authenticated if the user is private.
 
996
 
 
997
    Args:
 
998
      user:
 
999
        either the username (short_name) or id of the user to retrieve.  If
 
1000
        not specified, then the current authenticated user is used. [optional]
 
1001
      count: the number of status messages to retrieve [optional]
 
1002
      since:
 
1003
        Narrows the returned results to just those statuses created
 
1004
        after the specified HTTP-formatted date. [optional]
 
1005
 
 
1006
    Returns:
 
1007
      A sequence of twitter.Status instances, one for each message up to count
 
1008
 
 
1009
 
 
1010
    '''
 
1011
    try:
 
1012
      if count:
 
1013
        int(count)
 
1014
    except:
 
1015
      raise TwitterError("Count must be an integer")
 
1016
    parameters = {}
 
1017
    if count:
 
1018
      parameters['count'] = count
 
1019
    if since:
 
1020
      parameters['since'] = since
 
1021
    if user:
 
1022
      url = 'https://identi.ca/api/statuses/user_timeline/%s.json' % user
 
1023
    elif not user and not self._username:
 
1024
      raise TwitterError("User must be specified if API is not authenticated.")
 
1025
    else:
 
1026
      url = 'https://identi.ca/api/statuses/user_timeline.json'
 
1027
    json = self._FetchUrl(url, parameters=parameters)
 
1028
    data = simplejson.loads(json)
 
1029
    return [Status.NewFromJsonDict(x) for x in data]
 
1030
 
 
1031
  def GetStatus(self, id):
 
1032
    '''Returns a single status message.
 
1033
 
 
1034
    The twitter.Api instance must be authenticated if the status message is private.
 
1035
 
 
1036
    Args:
 
1037
      id: The numerical ID of the status you're trying to retrieve.
 
1038
 
 
1039
    Returns:
 
1040
      A twitter.Status instance representing that status message
 
1041
 
 
1042
 
 
1043
    '''
 
1044
    try:
 
1045
      if id:
 
1046
        int(id)
 
1047
    except:
 
1048
      raise TwitterError("id must be an integer")
 
1049
    url = 'https://identi.ca/api/statuses/show/%s.json' % id
 
1050
    json = self._FetchUrl(url)
 
1051
    data = simplejson.loads(json)
 
1052
    return Status.NewFromJsonDict(data)
 
1053
 
 
1054
  def DestroyStatus(self, id):
 
1055
    '''Destroys the status specified by the required ID parameter.
 
1056
 
 
1057
    The twitter.Api instance must be authenticated and thee
 
1058
    authenticating user must be the author of the specified status.
 
1059
 
 
1060
    Args:
 
1061
      id: The numerical ID of the status you're trying to destroy.
 
1062
 
 
1063
    Returns:
 
1064
      A twitter.Status instance representing the destroyed status message
 
1065
 
 
1066
 
 
1067
    '''
 
1068
    try:
 
1069
      if id:
 
1070
        int(id)
 
1071
    except:
 
1072
      raise TwitterError("id must be an integer")
 
1073
    url = 'https://identi.ca/api/statuses/destroy/%s.json' % id
 
1074
    json = self._FetchUrl(url, post_data={})
 
1075
    data = simplejson.loads(json)
 
1076
    return Status.NewFromJsonDict(data)
 
1077
 
 
1078
  def PostUpdate(self, text):
 
1079
    '''Post a twitter status message from the authenticated user.
 
1080
 
 
1081
    The twitter.Api instance must be authenticated.
 
1082
 
 
1083
    Args:
 
1084
      text: The message text to be posted.  Must be less than 140 characters.
 
1085
 
 
1086
    Returns:
 
1087
      A twitter.Status instance representing the message posted
 
1088
 
 
1089
 
 
1090
    '''
 
1091
    if not self._username:
 
1092
      raise TwitterError("The twitter.Api instance must be authenticated.")
 
1093
    if len(text) > 140:
 
1094
      raise TwitterError("Text must be less than or equal to 140 characters.")
 
1095
    url = 'https://identi.ca/api/statuses/update.json'
 
1096
    data = {'status': text}
 
1097
    json = self._FetchUrl(url, post_data=data)
 
1098
    data = simplejson.loads(json)
 
1099
    return Status.NewFromJsonDict(data)
 
1100
 
 
1101
  def GetReplies(self):
 
1102
    '''Get a sequence of status messages representing the 20 most recent
 
1103
    replies (status updates prefixed with @username) to the authenticating
 
1104
    user.
 
1105
 
 
1106
    Returns:
 
1107
      A sequence of twitter.Status instances, one for each reply to the user.
 
1108
 
 
1109
 
 
1110
    '''
 
1111
    url = 'https://identi.ca/api/statuses/replies.json'
 
1112
    if not self._username:
 
1113
      raise TwitterError("The twitter.Api instance must be authenticated.")
 
1114
    json = self._FetchUrl(url)
 
1115
    data = simplejson.loads(json)
 
1116
    return [Status.NewFromJsonDict(x) for x in data]
 
1117
 
 
1118
  def GetFriends(self, user=None):
 
1119
    '''Fetch the sequence of twitter.User instances, one for each friend.
 
1120
 
 
1121
    Args:
 
1122
      user: the username or id of the user whose friends you are fetching.  If
 
1123
      not specified, defaults to the authenticated user. [optional]
 
1124
 
 
1125
    The twitter.Api instance must be authenticated.
 
1126
 
 
1127
    Returns:
 
1128
      A sequence of twitter.User instances, one for each friend
 
1129
 
 
1130
 
 
1131
    '''
 
1132
    if not self._username:
 
1133
      raise TwitterError("twitter.Api instance must be authenticated")
 
1134
    if user:
 
1135
      url = 'https://identi.ca/api/statuses/friends/%s.json' % user
 
1136
    else:
 
1137
      url = 'https://identi.ca/api/statuses/friends.json'
 
1138
    json = self._FetchUrl(url)
 
1139
    data = simplejson.loads(json)
 
1140
    return [User.NewFromJsonDict(x) for x in data]
 
1141
 
 
1142
  def GetFollowers(self):
 
1143
    '''Fetch the sequence of twitter.User instances, one for each follower
 
1144
 
 
1145
    The twitter.Api instance must be authenticated.
 
1146
 
 
1147
    Returns:
 
1148
      A sequence of twitter.User instances, one for each follower
 
1149
 
 
1150
 
 
1151
    '''
 
1152
    if not self._username:
 
1153
      raise TwitterError("twitter.Api instance must be authenticated")
 
1154
    url = 'https://identi.ca/api/statuses/followers.json'
 
1155
    json = self._FetchUrl(url)
 
1156
    data = simplejson.loads(json)
 
1157
    return [User.NewFromJsonDict(x) for x in data]
 
1158
 
 
1159
  def GetFeatured(self):
 
1160
    '''Fetch the sequence of twitter.User instances featured on twitter.com
 
1161
 
 
1162
    The twitter.Api instance must be authenticated.
 
1163
 
 
1164
    Returns:
 
1165
      A sequence of twitter.User instances
 
1166
 
 
1167
 
 
1168
    '''
 
1169
    url = 'https://identi.ca/api/statuses/featured.json'
 
1170
    json = self._FetchUrl(url)
 
1171
    data = simplejson.loads(json)
 
1172
    return [User.NewFromJsonDict(x) for x in data]
 
1173
 
 
1174
  def GetUser(self, user):
 
1175
    '''Returns a single user.
 
1176
 
 
1177
    The twitter.Api instance must be authenticated.
 
1178
 
 
1179
    Args:
 
1180
      user: The username or id of the user to retrieve.
 
1181
 
 
1182
    Returns:
 
1183
      A twitter.User instance representing that user
 
1184
 
 
1185
 
 
1186
    '''
 
1187
    url = 'https://identi.ca/api/users/show/%s.json' % user
 
1188
    json = self._FetchUrl(url)
 
1189
    data = simplejson.loads(json)
 
1190
    return User.NewFromJsonDict(data)
 
1191
 
 
1192
  def GetDirectMessages(self, since=None):
 
1193
    '''Returns a list of the direct messages sent to the authenticating user.
 
1194
 
 
1195
    The twitter.Api instance must be authenticated.
 
1196
 
 
1197
    Args:
 
1198
      since:
 
1199
        Narrows the returned results to just those statuses created
 
1200
        after the specified HTTP-formatted date. [optional]
 
1201
 
 
1202
    Returns:
 
1203
      A sequence of twitter.DirectMessage instances
 
1204
 
 
1205
 
 
1206
    '''
 
1207
    url = 'https://identi.ca/api/direct_messages.json'
 
1208
    if not self._username:
 
1209
      raise TwitterError("The twitter.Api instance must be authenticated.")
 
1210
    parameters = {}
 
1211
    if since:
 
1212
      parameters['since'] = since
 
1213
    json = self._FetchUrl(url, parameters=parameters)
 
1214
    data = simplejson.loads(json)
 
1215
    return [DirectMessage.NewFromJsonDict(x) for x in data]
 
1216
 
 
1217
  def PostDirectMessage(self, user, text):
 
1218
    '''Post a twitter direct message from the authenticated user
 
1219
 
 
1220
    The twitter.Api instance must be authenticated.
 
1221
 
 
1222
    Args:
 
1223
      user: The ID or screen name of the recipient user.
 
1224
      text: The message text to be posted.  Must be less than 140 characters.
 
1225
 
 
1226
    Returns:
 
1227
      A twitter.DirectMessage instance representing the message posted
 
1228
 
 
1229
 
 
1230
    '''
 
1231
    if not self._username:
 
1232
      raise TwitterError("The twitter.Api instance must be authenticated.")
 
1233
    url = 'https://identi.ca/api/direct_messages/new.json'
 
1234
    data = {'text': text, 'user': user}
 
1235
    json = self._FetchUrl(url, post_data=data)
 
1236
    data = simplejson.loads(json)
 
1237
    return DirectMessage.NewFromJsonDict(data)
 
1238
 
 
1239
  def DestroyDirectMessage(self, id):
 
1240
    '''Destroys the direct message specified in the required ID parameter.
 
1241
 
 
1242
    The twitter.Api instance must be authenticated, and the
 
1243
    authenticating user must be the recipient of the specified direct
 
1244
    message.
 
1245
 
 
1246
    Args:
 
1247
      id: The id of the direct message to be destroyed
 
1248
 
 
1249
    Returns:
 
1250
      A twitter.DirectMessage instance representing the message destroyed
 
1251
 
 
1252
 
 
1253
    '''
 
1254
    url = 'https://identi.ca/api/direct_messages/destroy/%s.json' % id
 
1255
    json = self._FetchUrl(url, post_data={})
 
1256
    data = simplejson.loads(json)
 
1257
    return DirectMessage.NewFromJsonDict(data)
 
1258
 
 
1259
  def CreateFriendship(self, user):
 
1260
    '''Befriends the user specified in the user parameter as the authenticating user.
 
1261
 
 
1262
    The twitter.Api instance must be authenticated.
 
1263
 
 
1264
    Args:
 
1265
      The ID or screen name of the user to befriend.
 
1266
    Returns:
 
1267
      A twitter.User instance representing the befriended user.
 
1268
 
 
1269
 
 
1270
    '''
 
1271
    url = 'https://identi.ca/api/friendships/create/%s.json' % user
 
1272
    json = self._FetchUrl(url, post_data={})
 
1273
    data = simplejson.loads(json)
 
1274
    return User.NewFromJsonDict(data)
 
1275
 
 
1276
  def DestroyFriendship(self, user):
 
1277
    '''Discontinues friendship with the user specified in the user parameter.
 
1278
 
 
1279
    The twitter.Api instance must be authenticated.
 
1280
 
 
1281
    Args:
 
1282
      The ID or screen name of the user  with whom to discontinue friendship.
 
1283
    Returns:
 
1284
      A twitter.User instance representing the discontinued friend.
 
1285
 
 
1286
 
 
1287
    '''
 
1288
    url = 'https://identi.ca/api/friendships/destroy/%s.json' % user
 
1289
    json = self._FetchUrl(url, post_data={})
 
1290
    data = simplejson.loads(json)
 
1291
    return User.NewFromJsonDict(data)
 
1292
 
 
1293
  def SetCredentials(self, username, password):
 
1294
    '''Set the username and password for this instance
 
1295
 
 
1296
    Args:
 
1297
      username: The twitter username.
 
1298
      password: The twitter password.
 
1299
 
 
1300
 
 
1301
    '''
 
1302
    self._username = username
 
1303
    self._password = password
 
1304
 
 
1305
  def ClearCredentials(self):
 
1306
    '''Clear the username and password for this instance
 
1307
    '''
 
1308
    self._username = None
 
1309
    self._password = None
 
1310
 
 
1311
  def SetCache(self, cache):
 
1312
    '''Override the default cache.  Set to None to prevent caching.
 
1313
 
 
1314
    Args:
 
1315
      cache: an instance that supports the same API as the  twitter._FileCache
 
1316
 
 
1317
 
 
1318
    '''
 
1319
    self._cache = cache
 
1320
 
 
1321
  def SetUrllib(self, urllib):
 
1322
    '''Override the default urllib implementation.
 
1323
 
 
1324
    Args:
 
1325
      urllib: an instance that supports the same API as the urllib2 module
 
1326
 
 
1327
 
 
1328
    '''
 
1329
    self._urllib = urllib
 
1330
 
 
1331
  def SetCacheTimeout(self, cache_timeout):
 
1332
    '''Override the default cache timeout.
 
1333
 
 
1334
    Args:
 
1335
      cache_timeout: time, in seconds, that responses should be reused.
 
1336
 
 
1337
 
 
1338
    '''
 
1339
    self._cache_timeout = cache_timeout
 
1340
 
 
1341
  def SetUserAgent(self, user_agent):
 
1342
    '''Override the default user agent
 
1343
 
 
1344
    Args:
 
1345
      user_agent: a string that should be send to the server as the User-agent
 
1346
 
 
1347
 
 
1348
    '''
 
1349
    self._request_headers['User-Agent'] = user_agent
 
1350
 
 
1351
  def SetXTwitterHeaders(self, client, url, version):
 
1352
    '''Set the X-Twitter HTTP headers that will be sent to the server.
 
1353
 
 
1354
    Args:
 
1355
      client:
 
1356
         The client name as a string.  Will be sent to the server as
 
1357
         the 'X-Twitter-Client' header.
 
1358
      url:
 
1359
         The URL of the meta.xml as a string.  Will be sent to the server
 
1360
         as the 'X-Twitter-Client-URL' header.
 
1361
      version:
 
1362
         The client version as a string.  Will be sent to the server
 
1363
         as the 'X-Twitter-Client-Version' header.
 
1364
 
 
1365
 
 
1366
    '''
 
1367
    self._request_headers['X-Twitter-Client'] = client
 
1368
    self._request_headers['X-Twitter-Client-URL'] = url
 
1369
    self._request_headers['X-Twitter-Client-Version'] = version
 
1370
 
 
1371
  def _BuildUrl(self, url, path_elements=None, extra_params=None):
 
1372
    # Break url into consituent parts
 
1373
    (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
 
1374
 
 
1375
    # Add any additional path elements to the path
 
1376
    if path_elements:
 
1377
      # Filter out the path elements that have a value of None
 
1378
      p = [i for i in path_elements if i]
 
1379
      if not path.endswith('/'):
 
1380
        path += '/'
 
1381
      path += '/'.join(p)
 
1382
 
 
1383
    # Add any additional query parameters to the query string
 
1384
    if extra_params and len(extra_params) > 0:
 
1385
      extra_query = self._EncodeParameters(extra_params)
 
1386
      # Add it to the existing query
 
1387
      if query:
 
1388
        query += '&' + extra_query
 
1389
      else:
 
1390
        query = extra_query
 
1391
 
 
1392
    # Return the rebuilt URL
 
1393
    return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
 
1394
 
 
1395
  def _InitializeRequestHeaders(self, request_headers):
 
1396
    if request_headers:
 
1397
      self._request_headers = request_headers
 
1398
    else:
 
1399
      self._request_headers = {}
 
1400
 
 
1401
  def _InitializeUserAgent(self):
 
1402
    user_agent = 'Python-urllib/%s (python-twitter/0.5)' % \
 
1403
                 (self._urllib.__version__)
 
1404
    self.SetUserAgent(user_agent)
 
1405
 
 
1406
  def _AddAuthorizationHeader(self, username, password):
 
1407
    if username and password:
 
1408
      basic_auth = base64.encodestring('%s:%s' % (username, password))[:-1]
 
1409
      self._request_headers['Authorization'] = 'Basic %s' % basic_auth
 
1410
 
 
1411
  def _RemoveAuthorizationHeader(self):
 
1412
    if self._request_headers and 'Authorization' in self._request_headers:
 
1413
      del self._request_headers['Authorization']
 
1414
 
 
1415
  def _GetOpener(self, url, username=None, password=None):
 
1416
    if username and password:
 
1417
      self._AddAuthorizationHeader(username, password)
 
1418
      handler = self._urllib.HTTPBasicAuthHandler()
 
1419
      (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
 
1420
      handler.add_password(Api._API_REALM, netloc, username, password)
 
1421
      opener = self._urllib.build_opener(handler)
 
1422
    else:
 
1423
      opener = self._urllib.build_opener()
 
1424
    opener.addheaders = self._request_headers.items()
 
1425
    return opener
 
1426
 
 
1427
  def _Encode(self, s):
 
1428
    if self._input_encoding:
 
1429
      return unicode(s, self._input_encoding).encode('utf-8')
 
1430
    else:
 
1431
      return unicode(s).encode('utf-8')
 
1432
 
 
1433
  def _EncodeParameters(self, parameters):
 
1434
    '''Return a string in key=value&key=value form
 
1435
 
 
1436
    Values of None are not included in the output string.
 
1437
 
 
1438
    Args:
 
1439
      parameters:
 
1440
        A dict of (key, value) tuples, where value is encoded as
 
1441
        specified by self._encoding
 
1442
    Returns:
 
1443
      A URL-encoded string in "key=value&key=value" form
 
1444
 
 
1445
 
 
1446
    '''
 
1447
    if parameters is None:
 
1448
      return None
 
1449
    else:
 
1450
      return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in parameters.items() if v is not None]))
 
1451
 
 
1452
  def _EncodePostData(self, post_data):
 
1453
    '''Return a string in key=value&key=value form
 
1454
 
 
1455
    Values are assumed to be encoded in the format specified by self._encoding,
 
1456
    and are subsequently URL encoded.
 
1457
 
 
1458
    Args:
 
1459
      post_data:
 
1460
        A dict of (key, value) tuples, where value is encoded as
 
1461
        specified by self._encoding
 
1462
    Returns:
 
1463
      A URL-encoded string in "key=value&key=value" form
 
1464
 
 
1465
 
 
1466
    '''
 
1467
    if post_data is None:
 
1468
      return None
 
1469
    else:
 
1470
      return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in post_data.items()]))
 
1471
 
 
1472
  def _FetchUrl(self,
 
1473
                url,
 
1474
                post_data={},
 
1475
                parameters=None,
 
1476
                no_cache=None):
 
1477
    """Fetch a URL, optionally caching for a specified time.
 
1478
 
 
1479
    Args:
 
1480
      url: The URL to retrieve
 
1481
      data: A dict of (str, unicode) key value pairs.  If set, POST will be used.
 
1482
      parameters: A dict of key/value pairs that should added to
 
1483
                  the query string. [OPTIONAL]
 
1484
      username: A HTTP Basic Auth username for this request
 
1485
      username: A HTTP Basic Auth password for this request
 
1486
      no_cache: If true, overrides the cache on the current request
 
1487
 
 
1488
    Returns:
 
1489
      A string containing the body of the response.
 
1490
 
 
1491
 
 
1492
    """
 
1493
    # Add key/value parameters to the query string of the url
 
1494
    url = self._BuildUrl(url, extra_params=parameters)
 
1495
 
 
1496
    # Get a url opener that can handle basic auth
 
1497
    opener = self._GetOpener(url, username=self._username, password=self._password)
 
1498
 
 
1499
    if not 'source' in post_data:
 
1500
        post_data['source'] = 'gozerbot'
 
1501
    encoded_post_data = self._EncodePostData(post_data)
 
1502
 
 
1503
    # Open and return the URL immediately if we're not going to cache
 
1504
    if encoded_post_data or no_cache or not self._cache or not self._cache_timeout:
 
1505
      url_data = opener.open(url, encoded_post_data).read()
 
1506
    else:
 
1507
      # Unique keys are a combination of the url and the username
 
1508
      if self._username:
 
1509
        key = self._username + ':' + url
 
1510
      else:
 
1511
        key = url
 
1512
 
 
1513
      # See if it has been cached before
 
1514
      last_cached = self._cache.GetCachedTime(key)
 
1515
 
 
1516
      # If the cached version is outdated then fetch another and store it
 
1517
      if not last_cached or time.time() >= last_cached + self._cache_timeout:
 
1518
        url_data = opener.open(url, encoded_post_data).read()
 
1519
        self._cache.Set(key, url_data)
 
1520
      else:
 
1521
        url_data = self._cache.Get(key)
 
1522
 
 
1523
    # Always return the latest version
 
1524
    return url_data
 
1525
 
 
1526
class _FileCacheError(Exception):
 
1527
  '''Base exception class for FileCache related errors'''
 
1528
 
 
1529
class _FileCache(object):
 
1530
 
 
1531
  DEPTH = 3
 
1532
 
 
1533
  def __init__(self,root_directory=None):
 
1534
    self._InitializeRootDirectory(root_directory)
 
1535
 
 
1536
  def Get(self,key):
 
1537
    path = self._GetPath(key)
 
1538
    if os.path.exists(path):
 
1539
      return open(path).read()
 
1540
    else:
 
1541
      return None
 
1542
 
 
1543
  def Set(self,key,data):
 
1544
    path = self._GetPath(key)
 
1545
    directory = os.path.dirname(path)
 
1546
    if not os.path.exists(directory):
 
1547
      os.makedirs(directory)
 
1548
    if not os.path.isdir(directory):
 
1549
      raise _FileCacheError('%s exists but is not a directory' % directory)
 
1550
    temp_fd, temp_path = tempfile.mkstemp()
 
1551
    temp_fp = os.fdopen(temp_fd, 'w')
 
1552
    temp_fp.write(data)
 
1553
    temp_fp.close()
 
1554
    if not path.startswith(self._root_directory):
 
1555
      raise _FileCacheError('%s does not appear to live under %s' %
 
1556
                            (path, self._root_directory))
 
1557
    if os.path.exists(path):
 
1558
      os.remove(path)
 
1559
    os.rename(temp_path, path)
 
1560
 
 
1561
  def Remove(self,key):
 
1562
    path = self._GetPath(key)
 
1563
    if not path.startswith(self._root_directory):
 
1564
      raise _FileCacheError('%s does not appear to live under %s' %
 
1565
                            (path, self._root_directory ))
 
1566
    if os.path.exists(path):
 
1567
      os.remove(path)
 
1568
 
 
1569
  def GetCachedTime(self,key):
 
1570
    path = self._GetPath(key)
 
1571
    if os.path.exists(path):
 
1572
      return os.path.getmtime(path)
 
1573
    else:
 
1574
      return None
 
1575
 
 
1576
  def _GetUsername(self):
 
1577
    '''Attempt to find the username in a cross-platform fashion.'''
 
1578
    return os.getenv('USER') or \
 
1579
        os.getenv('LOGNAME') or \
 
1580
        os.getenv('USERNAME') or \
 
1581
        'nobody'
 
1582
 
 
1583
  def _GetTmpCachePath(self):
 
1584
    username = self._GetUsername()
 
1585
    cache_directory = 'python.cache_' + username
 
1586
    return os.path.join(tempfile.gettempdir(), cache_directory)
 
1587
 
 
1588
  def _InitializeRootDirectory(self, root_directory):
 
1589
    if not root_directory:
 
1590
      root_directory = self._GetTmpCachePath()
 
1591
    root_directory = os.path.abspath(root_directory)
 
1592
    if not os.path.exists(root_directory):
 
1593
      os.mkdir(root_directory)
 
1594
    if not os.path.isdir(root_directory):
 
1595
      raise _FileCacheError('%s exists but is not a directory' %
 
1596
                            root_directory)
 
1597
    self._root_directory = root_directory
 
1598
 
 
1599
  def _GetPath(self,key):
 
1600
    hashed_key = hashlib.md5.new(key).hexdigest()
 
1601
    return os.path.join(self._root_directory,
 
1602
                        self._GetPrefix(hashed_key),
 
1603
                        hashed_key)
 
1604
 
 
1605
  def _GetPrefix(self,hashed_key):
 
1606
    return os.path.sep.join(hashed_key[0:_FileCache.DEPTH])
 
1607
 
 
1608
#
 
1609
# gozerbot stuff
 
1610
#
 
1611
 
 
1612
def identiapi(**kwargs):
 
1613
    api = Api(**kwargs)
 
1614
    #api.SetXTwitterHeaders('gozerbot', 'http://gozerbot.org', __version__)
 
1615
    return api
 
1616
 
 
1617
class IdentiUser(Pdol):
 
1618
    def __init__(self):
 
1619
        Pdol.__init__(self, os.path.join(datadir + os.sep + 'plugs' + \
 
1620
os.sep + 'identi', 'identi'))
 
1621
 
 
1622
    def add(self, user, username, password):
 
1623
        self.data[user] = [username, password]
 
1624
        self.save()
 
1625
 
 
1626
    def remove(self, user):
 
1627
        if user in self.data:
 
1628
            del self.data[user]
 
1629
            self.save()
 
1630
 
 
1631
    def size(self):
 
1632
        return len(self.data)
 
1633
 
 
1634
    def __contains__(self, user):
 
1635
        return user in self.data
 
1636
 
 
1637
# VARS SECTION
 
1638
 
 
1639
identiuser = IdentiUser()
 
1640
 
 
1641
# END VARS
 
1642
 
 
1643
def upgrade():
 
1644
    pass
 
1645
 
 
1646
def size():
 
1647
    return identiuser.size()
 
1648
 
 
1649
def handle_identi(bot, ievent):
 
1650
 
 
1651
    """ send a identi.ca message. """
 
1652
 
 
1653
    user = users.getname(ievent.userhost)
 
1654
 
 
1655
    if not user:
 
1656
        ievent.reply("can't find user for %s" % ievent.userhost)
 
1657
        return
 
1658
 
 
1659
    if not user in identiuser:
 
1660
        ievent.reply('you need a identi.ca account for this command, '
 
1661
                     'use "!identi-id <username> <password>"'
 
1662
                     'first (in a private message!)')
 
1663
        return
 
1664
    if ievent.inqueue:
 
1665
        result = waitforqueue(ievent.inqueue, 30)
 
1666
    elif not ievent.rest:
 
1667
        ievent.missing('<text>')
 
1668
        return
 
1669
    else:
 
1670
        result = [ievent.rest, ]
 
1671
    credentials = identiuser.get(user.name)
 
1672
    try:
 
1673
        api = identiapi(username=credentials[0], password=credentials[1])
 
1674
        for txt in result:
 
1675
            status = api.PostUpdate(txt[:119])
 
1676
        ievent.reply('blurp posted to https://identi.ca/%s ' % (credentials[0],))
 
1677
    except (TwitterError, urllib2.HTTPError), e:
 
1678
        ievent.reply('identi failed: %s' % (str(e),))
 
1679
 
 
1680
cmnds.add('identi', handle_identi, 'USER')
 
1681
examples.add('identi', 'adds a message to your identi.ca account', 
 
1682
    'identi.ca just found the https://gozerbot.org project')
 
1683
 
 
1684
def handle_identi_friends(bot, ievent):
 
1685
 
 
1686
    """ show identi.ca friends. """
 
1687
 
 
1688
    user = users.getname(ievent.userhost)
 
1689
 
 
1690
    if not user:
 
1691
        ievent.reply("can't find user for %s" % ievent.userhost)
 
1692
        return
 
1693
 
 
1694
    if not user in identiuser:
 
1695
        ievent.reply('you need a identi.ca account for this command, '
 
1696
                     'use "!identi-id <username> <password>" '
 
1697
                     'first (in a private message!)')
 
1698
    else:
 
1699
        credentials = identiuser.get(ievent.user)
 
1700
        try:
 
1701
            api = identiapi(username=credentials[0], password=credentials[1])
 
1702
            users = api.GetFriends()
 
1703
            users = ['%s (%s)' % (u.name, u.screen_name) for u in users]
 
1704
            if users:
 
1705
                users.sort()
 
1706
                ievent.reply('your identi.ca friends: ', users, dot=True)
 
1707
            else:
 
1708
                ievent.reply('poor you, no friends!')
 
1709
        except Exception, e:
 
1710
            ievent.reply('identi failed: %s' % (str(e),))
 
1711
 
 
1712
cmnds.add('identi-friends', handle_identi_friends, 'USER')
 
1713
examples.add('identi-friends', 'shows your identi.ca friends', 'identi-friends')
 
1714
 
 
1715
def handle_identi_get(bot, ievent):
 
1716
 
 
1717
    """ get identi messages. """
 
1718
 
 
1719
    if not ievent.args:
 
1720
        ievent.missing('<username>')
 
1721
    else:
 
1722
        try:
 
1723
            api = identiapi()
 
1724
            status = api.GetUserTimeline(ievent.args[0])
 
1725
            if status:
 
1726
                ievent.reply([html_unescape(s.text) for s in status], dot=True)
 
1727
            else:
 
1728
                ievent.reply('no result')
 
1729
        except (TwitterError, urllib2.HTTPError), e:
 
1730
            ievent.reply('identi failed: %s' % (str(e),))
 
1731
 
 
1732
cmnds.add('identi-get', handle_identi_get, 'USER')
 
1733
examples.add('identi-get', 'gets the identi messages for a user', 'identi-get tehmaze')
 
1734
 
 
1735
def handle_identi_id(bot, ievent):
 
1736
 
 
1737
    """ set identi.ca id. """
 
1738
 
 
1739
    user = users.getname(ievent.userhost)
 
1740
 
 
1741
    if not user:
 
1742
        ievent.reply("can't find user for %s" % ievent.userhost)
 
1743
        return
 
1744
 
 
1745
    if ievent.channel[0] in '#!&':
 
1746
        ievent.reply('%s: use a private message!' % (ievent.nick,))
 
1747
        return
 
1748
    elif len(ievent.args) != 2:
 
1749
        ievent.reply('identi-id <username> <password>')
 
1750
    else:
 
1751
        identiuser.add(user.name, ievent.args[0], ievent.args[1])
 
1752
        ievent.reply('ok')
 
1753
 
 
1754
cmnds.add('identi-id', handle_identi_id, 'USER')
 
1755
examples.add('identi-id', 'adds your identi.ca account', 'identi-id example secret')