3
# based on the Twitter post plugin
4
# (c) Wijnand 'maze' Modderman <http://tehmaze.com>
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.
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
27
plughelp.add('identi', 'do the identi.ca')
35
# (c) DeWitt Clinton <dewitt@google.com>
49
class TwitterError(Exception):
50
'''Base class for Twitter errors'''
53
'''A class representing the Status structure used by the twitter API.
55
The Status structure exposes the following properties:
58
status.created_at_in_seconds # read only
61
status.relative_created_at # read only
72
'''An object to hold a Twitter status message.
74
This class is normally instantiated by the twitter.Api class and
75
returned in a sequence.
77
Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007"
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
84
A human readable string representing the posting time
86
A twitter.User instance representing the person posting the message
88
The current time, if the client choses to set it. Defaults to the
93
self.created_at = created_at
99
def GetCreatedAt(self):
100
'''Get the time this status message was posted.
103
The time this status message was posted
105
return self._created_at
107
def SetCreatedAt(self, created_at):
108
'''Set the time this status message was posted.
111
created_at: The time this status message was created
113
self._created_at = created_at
115
created_at = property(GetCreatedAt, SetCreatedAt,
116
doc='The time this status message was posted.')
118
def GetCreatedAtInSeconds(self):
119
'''Get the time this status message was posted, in seconds since the epoch.
122
The time this status message was posted, in seconds since the epoch.
124
return time.mktime(time.strptime(self.created_at, '%a %b %d %H:%M:%S +0000 %Y'))
126
created_at_in_seconds = property(GetCreatedAtInSeconds,
127
doc="The time this status message was "
128
"posted, in seconds since the epoch")
131
'''Get the unique id of this status message.
134
The unique id of this status message
139
'''Set the unique id of this status message.
142
id: The unique id of this status message
146
id = property(GetId, SetId,
147
doc='The unique id of this status message.')
150
'''Get the text of this status message.
153
The text of this status message.
157
def SetText(self, text):
158
'''Set the text of this status message.
161
text: The text of this status message
165
text = property(GetText, SetText,
166
doc='The text of this status message')
168
def GetRelativeCreatedAt(self):
169
'''Get a human redable string representing the posting time
172
A human readable string representing the posting time
175
delta = int(self.now) - int(self.created_at_in_seconds)
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'
192
return 'about %d days ago' % (delta / (60 * 60 * 24))
194
relative_created_at = property(GetRelativeCreatedAt,
195
doc='Get a human readable string representing'
199
'''Get a twitter.User reprenting the entity posting this status message.
202
A twitter.User reprenting the entity posting this status message
206
def SetUser(self, user):
207
'''Set a twitter.User reprenting the entity posting this status message.
210
user: A twitter.User reprenting the entity posting this status message
214
user = property(GetUser, SetUser,
215
doc='A twitter.User reprenting the entity posting this '
219
'''Get the wallclock time for this status message.
221
Used to calculate relative_created_at. Defaults to the time
222
the object was instantiated.
225
Whatever the status instance believes the current time to be,
226
in seconds since the epoch.
228
if self._now is None:
229
self._now = time.mktime(time.gmtime())
232
def SetNow(self, now):
233
'''Set the wallclock time for this status message.
235
Used to calculate relative_created_at. Defaults to the time
236
the object was instantiated.
239
now: The wallclock time for this instance.
243
now = property(GetNow, SetNow,
244
doc='The wallclock time for this status instance.')
247
def __ne__(self, other):
248
return not self.__eq__(other)
250
def __eq__(self, other):
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:
261
'''A string representation of this twitter.Status instance.
263
The return value is the same as the JSON string representation.
266
A string representation of this twitter.Status instance.
268
return self.AsJsonString()
270
def AsJsonString(self):
271
'''A JSON string representation of this twitter.Status instance.
274
A JSON string representation of this twitter.Status instance
276
return simplejson.dumps(self.AsDict(), sort_keys=True)
279
'''A dict representation of this twitter.Status instance.
281
The return value uses the same key names as the JSON representation.
284
A dict representing this twitter.Status instance
288
data['created_at'] = self.created_at
292
data['text'] = self.text
294
data['user'] = self.user.AsDict()
298
def NewFromJsonDict(data):
299
'''Create a new instance based on a JSON dict.
302
data: A JSON dict, as converted from the JSON in the twitter API
304
A twitter.Status instance
307
user = User.NewFromJsonDict(data['user'])
310
return Status(created_at=data.get('created_at', None),
311
id=data.get('id', None),
312
text=data.get('text', None),
317
'''A class representing the User structure used by the twitter API.
319
The User structure exposes the following properties:
326
user.profile_image_url
336
profile_image_url=None,
341
self.screen_name = screen_name
342
self.location = location
343
self.description = description
344
self.profile_image_url = profile_image_url
350
'''Get the unique id of this user.
353
The unique id of this user
358
'''Set the unique id of this user.
361
id: The unique id of this user.
365
id = property(GetId, SetId,
366
doc='The unique id of this user.')
369
'''Get the real name of this user.
372
The real name of this user
376
def SetName(self, name):
377
'''Set the real name of this user.
380
name: The real name of this user
384
name = property(GetName, SetName,
385
doc='The real name of this user.')
387
def GetScreenName(self):
388
'''Get the short username of this user.
391
The short username of this user
393
return self._screen_name
395
def SetScreenName(self, screen_name):
396
'''Set the short username of this user.
399
screen_name: the short username of this user
401
self._screen_name = screen_name
403
screen_name = property(GetScreenName, SetScreenName,
404
doc='The short username of this user.')
406
def GetLocation(self):
407
'''Get the geographic location of this user.
410
The geographic location of this user
412
return self._location
414
def SetLocation(self, location):
415
'''Set the geographic location of this user.
418
location: The geographic location of this user
420
self._location = location
422
location = property(GetLocation, SetLocation,
423
doc='The geographic location of this user.')
425
def GetDescription(self):
426
'''Get the short text description of this user.
429
The short text description of this user
431
return self._description
433
def SetDescription(self, description):
434
'''Set the short text description of this user.
437
description: The short text description of this user
439
self._description = description
441
description = property(GetDescription, SetDescription,
442
doc='The short text description of this user.')
445
'''Get the homepage url of this user.
448
The homepage url of this user
452
def SetUrl(self, url):
453
'''Set the homepage url of this user.
456
url: The homepage url of this user
460
url = property(GetUrl, SetUrl,
461
doc='The homepage url of this user.')
463
def GetProfileImageUrl(self):
464
'''Get the url of the thumbnail of this user.
467
The url of the thumbnail of this user
469
return self._profile_image_url
471
def SetProfileImageUrl(self, profile_image_url):
472
'''Set the url of the thumbnail of this user.
475
profile_image_url: The url of the thumbnail of this user
477
self._profile_image_url = profile_image_url
479
profile_image_url= property(GetProfileImageUrl, SetProfileImageUrl,
480
doc='The url of the thumbnail of this user.')
483
'''Get the latest twitter.Status of this user.
486
The latest twitter.Status of this user
490
def SetStatus(self, status):
491
'''Set the latest twitter.Status of this user.
494
status: The latest twitter.Status of this user
496
self._status = status
498
status = property(GetStatus, SetStatus,
499
doc='The latest twitter.Status of this user.')
501
def __ne__(self, other):
502
return not self.__eq__(other)
504
def __eq__(self, other):
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:
519
'''A string representation of this twitter.User instance.
521
The return value is the same as the JSON string representation.
524
A string representation of this twitter.User instance.
526
return self.AsJsonString()
528
def AsJsonString(self):
529
'''A JSON string representation of this twitter.User instance.
532
A JSON string representation of this twitter.User instance
534
return simplejson.dumps(self.AsDict(), sort_keys=True)
537
'''A dict representation of this twitter.User instance.
539
The return value uses the same key names as the JSON representation.
542
A dict representing this twitter.User instance
548
data['name'] = self.name
550
data['screen_name'] = self.screen_name
552
data['location'] = self.location
554
data['description'] = self.description
555
if self.profile_image_url:
556
data['profile_image_url'] = self.profile_image_url
558
data['url'] = self.url
560
data['status'] = self.status.AsDict()
564
def NewFromJsonDict(data):
565
'''Create a new instance based on a JSON dict.
568
data: A JSON dict, as converted from the JSON in the twitter API
570
A twitter.User instance
573
status = Status.NewFromJsonDict(data['status'])
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),
585
class DirectMessage(object):
586
'''A class representing the DirectMessage structure used by the twitter API.
588
The DirectMessage structure exposes the following properties:
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
604
sender_screen_name=None,
606
recipient_screen_name=None,
608
'''An object to hold a Twitter direct message.
610
This class is normally instantiated by the twitter.Api class and
611
returned in a sequence.
613
Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007"
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
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
633
'''Get the unique id of this direct message.
636
The unique id of this direct message
641
'''Set the unique id of this direct message.
644
id: The unique id of this direct message
648
id = property(GetId, SetId,
649
doc='The unique id of this direct message.')
651
def GetCreatedAt(self):
652
'''Get the time this direct message was posted.
655
The time this direct message was posted
657
return self._created_at
659
def SetCreatedAt(self, created_at):
660
'''Set the time this direct message was posted.
663
created_at: The time this direct message was created
665
self._created_at = created_at
667
created_at = property(GetCreatedAt, SetCreatedAt,
668
doc='The time this direct message was posted.')
670
def GetCreatedAtInSeconds(self):
671
'''Get the time this direct message was posted, in seconds since the epoch.
674
The time this direct message was posted, in seconds since the epoch.
676
return time.mktime(time.strptime(self.created_at, '%a %b %d %H:%M:%S +0000 %Y'))
678
created_at_in_seconds = property(GetCreatedAtInSeconds,
679
doc="The time this direct message was "
680
"posted, in seconds since the epoch")
682
def GetSenderId(self):
683
'''Get the unique sender id of this direct message.
686
The unique sender id of this direct message
688
return self._sender_id
690
def SetSenderId(self, sender_id):
691
'''Set the unique sender id of this direct message.
694
sender id: The unique sender id of this direct message
696
self._sender_id = sender_id
698
sender_id = property(GetSenderId, SetSenderId,
699
doc='The unique sender id of this direct message.')
701
def GetSenderScreenName(self):
702
'''Get the unique sender screen name of this direct message.
705
The unique sender screen name of this direct message
707
return self._sender_screen_name
709
def SetSenderScreenName(self, sender_screen_name):
710
'''Set the unique sender screen name of this direct message.
713
sender_screen_name: The unique sender screen name of this direct message
715
self._sender_screen_name = sender_screen_name
717
sender_screen_name = property(GetSenderScreenName, SetSenderScreenName,
718
doc='The unique sender screen name of this direct message.')
720
def GetRecipientId(self):
721
'''Get the unique recipient id of this direct message.
724
The unique recipient id of this direct message
726
return self._recipient_id
728
def SetRecipientId(self, recipient_id):
729
'''Set the unique recipient id of this direct message.
732
recipient id: The unique recipient id of this direct message
734
self._recipient_id = recipient_id
736
recipient_id = property(GetRecipientId, SetRecipientId,
737
doc='The unique recipient id of this direct message.')
739
def GetRecipientScreenName(self):
740
'''Get the unique recipient screen name of this direct message.
743
The unique recipient screen name of this direct message
745
return self._recipient_screen_name
747
def SetRecipientScreenName(self, recipient_screen_name):
748
'''Set the unique recipient screen name of this direct message.
751
recipient_screen_name: The unique recipient screen name of this direct message
753
self._recipient_screen_name = recipient_screen_name
755
recipient_screen_name = property(GetRecipientScreenName, SetRecipientScreenName,
756
doc='The unique recipient screen name of this direct message.')
759
'''Get the text of this direct message.
762
The text of this direct message.
766
def SetText(self, text):
767
'''Set the text of this direct message.
770
text: The text of this direct message
774
text = property(GetText, SetText,
775
doc='The text of this direct message')
777
def __ne__(self, other):
778
return not self.__eq__(other)
780
def __eq__(self, other):
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:
794
'''A string representation of this twitter.DirectMessage instance.
796
The return value is the same as the JSON string representation.
799
A string representation of this twitter.DirectMessage instance.
801
return self.AsJsonString()
803
def AsJsonString(self):
804
'''A JSON string representation of this twitter.DirectMessage instance.
807
A JSON string representation of this twitter.DirectMessage instance
809
return simplejson.dumps(self.AsDict(), sort_keys=True)
812
'''A dict representation of this twitter.DirectMessage instance.
814
The return value uses the same key names as the JSON representation.
817
A dict representing this twitter.DirectMessage instance
823
data['created_at'] = self.created_at
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
833
data['text'] = self.text
837
def NewFromJsonDict(data):
838
'''Create a new instance based on a JSON dict.
841
data: A JSON dict, as converted from the JSON in the twitter API
843
A twitter.DirectMessage instance
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))
854
'''A python interface into the Twitter API
856
By default, the Api caches results for 1 minute.
860
To create an instance of the twitter.Api class, with no authentication:
863
>>> api = twitter.Api()
865
To fetch the most recently posted public twitter status messages:
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'] #...
871
To fetch a single user's public status messages, where "user" is either
872
a Twitter "short name" or their user id.
874
>>> statuses = api.GetUserTimeline(user)
875
>>> print [s.text for s in statuses]
877
To use authentication, instantiate the twitter.Api class with a
878
username and password:
880
>>> api = twitter.Api(username='twitter user', password='twitter pass')
882
To fetch your friends (after being authenticated):
884
>>> users = api.GetFriends()
885
>>> print [u.name for u in users]
887
To post a twitter status message (after being authenticated):
889
>>> status = api.PostUpdate('I love python-twitter!')
890
>>> print status.text
891
I love python-twitter!
893
There are many other methods, including:
895
>>> api.PostDirectMessage(user, text)
896
>>> api.GetUser(user)
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)
912
DEFAULT_CACHE_TIMEOUT = 60 # cache for 1 minute
914
_API_REALM = 'Twitter API'
920
request_headers=None):
921
'''Instantiate a new twitter.Api object.
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]
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)
939
def GetPublicTimeline(self, since_id=None):
940
'''Fetch the sequnce of public twitter.Status message for all users.
944
Returns only public statuses with an ID greater than (that is,
945
more recent than) the specified ID. [Optional]
948
An sequence of twitter.Status instances, one for each message
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]
960
def GetFriendsTimeline(self, user=None, since=None):
961
'''Fetch the sequence of twitter.Status messages for a user's friends
963
The twitter.Api instance must be authenticated if the user is private.
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]
971
Narrows the returned results to just those statuses created
972
after the specified HTTP-formatted date. [optional]
975
A sequence of twitter.Status instances, one for each message
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.")
984
url = 'https://identi.ca/api/statuses/friends_timeline.json'
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]
992
def GetUserTimeline(self, user=None, count=None, since=None):
993
'''Fetch the sequence of public twitter.Status messages for a single user.
995
The twitter.Api instance must be authenticated if the user is private.
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]
1003
Narrows the returned results to just those statuses created
1004
after the specified HTTP-formatted date. [optional]
1007
A sequence of twitter.Status instances, one for each message up to count
1015
raise TwitterError("Count must be an integer")
1018
parameters['count'] = count
1020
parameters['since'] = since
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.")
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]
1031
def GetStatus(self, id):
1032
'''Returns a single status message.
1034
The twitter.Api instance must be authenticated if the status message is private.
1037
id: The numerical ID of the status you're trying to retrieve.
1040
A twitter.Status instance representing that status message
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)
1054
def DestroyStatus(self, id):
1055
'''Destroys the status specified by the required ID parameter.
1057
The twitter.Api instance must be authenticated and thee
1058
authenticating user must be the author of the specified status.
1061
id: The numerical ID of the status you're trying to destroy.
1064
A twitter.Status instance representing the destroyed status message
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)
1078
def PostUpdate(self, text):
1079
'''Post a twitter status message from the authenticated user.
1081
The twitter.Api instance must be authenticated.
1084
text: The message text to be posted. Must be less than 140 characters.
1087
A twitter.Status instance representing the message posted
1091
if not self._username:
1092
raise TwitterError("The twitter.Api instance must be authenticated.")
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)
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
1107
A sequence of twitter.Status instances, one for each reply to the user.
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]
1118
def GetFriends(self, user=None):
1119
'''Fetch the sequence of twitter.User instances, one for each friend.
1122
user: the username or id of the user whose friends you are fetching. If
1123
not specified, defaults to the authenticated user. [optional]
1125
The twitter.Api instance must be authenticated.
1128
A sequence of twitter.User instances, one for each friend
1132
if not self._username:
1133
raise TwitterError("twitter.Api instance must be authenticated")
1135
url = 'https://identi.ca/api/statuses/friends/%s.json' % user
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]
1142
def GetFollowers(self):
1143
'''Fetch the sequence of twitter.User instances, one for each follower
1145
The twitter.Api instance must be authenticated.
1148
A sequence of twitter.User instances, one for each follower
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]
1159
def GetFeatured(self):
1160
'''Fetch the sequence of twitter.User instances featured on twitter.com
1162
The twitter.Api instance must be authenticated.
1165
A sequence of twitter.User instances
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]
1174
def GetUser(self, user):
1175
'''Returns a single user.
1177
The twitter.Api instance must be authenticated.
1180
user: The username or id of the user to retrieve.
1183
A twitter.User instance representing that user
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)
1192
def GetDirectMessages(self, since=None):
1193
'''Returns a list of the direct messages sent to the authenticating user.
1195
The twitter.Api instance must be authenticated.
1199
Narrows the returned results to just those statuses created
1200
after the specified HTTP-formatted date. [optional]
1203
A sequence of twitter.DirectMessage instances
1207
url = 'https://identi.ca/api/direct_messages.json'
1208
if not self._username:
1209
raise TwitterError("The twitter.Api instance must be authenticated.")
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]
1217
def PostDirectMessage(self, user, text):
1218
'''Post a twitter direct message from the authenticated user
1220
The twitter.Api instance must be authenticated.
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.
1227
A twitter.DirectMessage instance representing the message posted
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)
1239
def DestroyDirectMessage(self, id):
1240
'''Destroys the direct message specified in the required ID parameter.
1242
The twitter.Api instance must be authenticated, and the
1243
authenticating user must be the recipient of the specified direct
1247
id: The id of the direct message to be destroyed
1250
A twitter.DirectMessage instance representing the message destroyed
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)
1259
def CreateFriendship(self, user):
1260
'''Befriends the user specified in the user parameter as the authenticating user.
1262
The twitter.Api instance must be authenticated.
1265
The ID or screen name of the user to befriend.
1267
A twitter.User instance representing the befriended user.
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)
1276
def DestroyFriendship(self, user):
1277
'''Discontinues friendship with the user specified in the user parameter.
1279
The twitter.Api instance must be authenticated.
1282
The ID or screen name of the user with whom to discontinue friendship.
1284
A twitter.User instance representing the discontinued friend.
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)
1293
def SetCredentials(self, username, password):
1294
'''Set the username and password for this instance
1297
username: The twitter username.
1298
password: The twitter password.
1302
self._username = username
1303
self._password = password
1305
def ClearCredentials(self):
1306
'''Clear the username and password for this instance
1308
self._username = None
1309
self._password = None
1311
def SetCache(self, cache):
1312
'''Override the default cache. Set to None to prevent caching.
1315
cache: an instance that supports the same API as the twitter._FileCache
1321
def SetUrllib(self, urllib):
1322
'''Override the default urllib implementation.
1325
urllib: an instance that supports the same API as the urllib2 module
1329
self._urllib = urllib
1331
def SetCacheTimeout(self, cache_timeout):
1332
'''Override the default cache timeout.
1335
cache_timeout: time, in seconds, that responses should be reused.
1339
self._cache_timeout = cache_timeout
1341
def SetUserAgent(self, user_agent):
1342
'''Override the default user agent
1345
user_agent: a string that should be send to the server as the User-agent
1349
self._request_headers['User-Agent'] = user_agent
1351
def SetXTwitterHeaders(self, client, url, version):
1352
'''Set the X-Twitter HTTP headers that will be sent to the server.
1356
The client name as a string. Will be sent to the server as
1357
the 'X-Twitter-Client' header.
1359
The URL of the meta.xml as a string. Will be sent to the server
1360
as the 'X-Twitter-Client-URL' header.
1362
The client version as a string. Will be sent to the server
1363
as the 'X-Twitter-Client-Version' header.
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
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)
1375
# Add any additional path elements to the path
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('/'):
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
1388
query += '&' + extra_query
1392
# Return the rebuilt URL
1393
return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
1395
def _InitializeRequestHeaders(self, request_headers):
1397
self._request_headers = request_headers
1399
self._request_headers = {}
1401
def _InitializeUserAgent(self):
1402
user_agent = 'Python-urllib/%s (python-twitter/0.5)' % \
1403
(self._urllib.__version__)
1404
self.SetUserAgent(user_agent)
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
1411
def _RemoveAuthorizationHeader(self):
1412
if self._request_headers and 'Authorization' in self._request_headers:
1413
del self._request_headers['Authorization']
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)
1423
opener = self._urllib.build_opener()
1424
opener.addheaders = self._request_headers.items()
1427
def _Encode(self, s):
1428
if self._input_encoding:
1429
return unicode(s, self._input_encoding).encode('utf-8')
1431
return unicode(s).encode('utf-8')
1433
def _EncodeParameters(self, parameters):
1434
'''Return a string in key=value&key=value form
1436
Values of None are not included in the output string.
1440
A dict of (key, value) tuples, where value is encoded as
1441
specified by self._encoding
1443
A URL-encoded string in "key=value&key=value" form
1447
if parameters is None:
1450
return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in parameters.items() if v is not None]))
1452
def _EncodePostData(self, post_data):
1453
'''Return a string in key=value&key=value form
1455
Values are assumed to be encoded in the format specified by self._encoding,
1456
and are subsequently URL encoded.
1460
A dict of (key, value) tuples, where value is encoded as
1461
specified by self._encoding
1463
A URL-encoded string in "key=value&key=value" form
1467
if post_data is None:
1470
return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in post_data.items()]))
1477
"""Fetch a URL, optionally caching for a specified time.
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
1489
A string containing the body of the response.
1493
# Add key/value parameters to the query string of the url
1494
url = self._BuildUrl(url, extra_params=parameters)
1496
# Get a url opener that can handle basic auth
1497
opener = self._GetOpener(url, username=self._username, password=self._password)
1499
if not 'source' in post_data:
1500
post_data['source'] = 'gozerbot'
1501
encoded_post_data = self._EncodePostData(post_data)
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()
1507
# Unique keys are a combination of the url and the username
1509
key = self._username + ':' + url
1513
# See if it has been cached before
1514
last_cached = self._cache.GetCachedTime(key)
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)
1521
url_data = self._cache.Get(key)
1523
# Always return the latest version
1526
class _FileCacheError(Exception):
1527
'''Base exception class for FileCache related errors'''
1529
class _FileCache(object):
1533
def __init__(self,root_directory=None):
1534
self._InitializeRootDirectory(root_directory)
1537
path = self._GetPath(key)
1538
if os.path.exists(path):
1539
return open(path).read()
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')
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):
1559
os.rename(temp_path, path)
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):
1569
def GetCachedTime(self,key):
1570
path = self._GetPath(key)
1571
if os.path.exists(path):
1572
return os.path.getmtime(path)
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 \
1583
def _GetTmpCachePath(self):
1584
username = self._GetUsername()
1585
cache_directory = 'python.cache_' + username
1586
return os.path.join(tempfile.gettempdir(), cache_directory)
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' %
1597
self._root_directory = root_directory
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),
1605
def _GetPrefix(self,hashed_key):
1606
return os.path.sep.join(hashed_key[0:_FileCache.DEPTH])
1612
def identiapi(**kwargs):
1614
#api.SetXTwitterHeaders('gozerbot', 'http://gozerbot.org', __version__)
1617
class IdentiUser(Pdol):
1619
Pdol.__init__(self, os.path.join(datadir + os.sep + 'plugs' + \
1620
os.sep + 'identi', 'identi'))
1622
def add(self, user, username, password):
1623
self.data[user] = [username, password]
1626
def remove(self, user):
1627
if user in self.data:
1632
return len(self.data)
1634
def __contains__(self, user):
1635
return user in self.data
1639
identiuser = IdentiUser()
1647
return identiuser.size()
1649
def handle_identi(bot, ievent):
1651
""" send a identi.ca message. """
1653
user = users.getname(ievent.userhost)
1656
ievent.reply("can't find user for %s" % ievent.userhost)
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!)')
1665
result = waitforqueue(ievent.inqueue, 30)
1666
elif not ievent.rest:
1667
ievent.missing('<text>')
1670
result = [ievent.rest, ]
1671
credentials = identiuser.get(user.name)
1673
api = identiapi(username=credentials[0], password=credentials[1])
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),))
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')
1684
def handle_identi_friends(bot, ievent):
1686
""" show identi.ca friends. """
1688
user = users.getname(ievent.userhost)
1691
ievent.reply("can't find user for %s" % ievent.userhost)
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!)')
1699
credentials = identiuser.get(ievent.user)
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]
1706
ievent.reply('your identi.ca friends: ', users, dot=True)
1708
ievent.reply('poor you, no friends!')
1709
except Exception, e:
1710
ievent.reply('identi failed: %s' % (str(e),))
1712
cmnds.add('identi-friends', handle_identi_friends, 'USER')
1713
examples.add('identi-friends', 'shows your identi.ca friends', 'identi-friends')
1715
def handle_identi_get(bot, ievent):
1717
""" get identi messages. """
1720
ievent.missing('<username>')
1724
status = api.GetUserTimeline(ievent.args[0])
1726
ievent.reply([html_unescape(s.text) for s in status], dot=True)
1728
ievent.reply('no result')
1729
except (TwitterError, urllib2.HTTPError), e:
1730
ievent.reply('identi failed: %s' % (str(e),))
1732
cmnds.add('identi-get', handle_identi_get, 'USER')
1733
examples.add('identi-get', 'gets the identi messages for a user', 'identi-get tehmaze')
1735
def handle_identi_id(bot, ievent):
1737
""" set identi.ca id. """
1739
user = users.getname(ievent.userhost)
1742
ievent.reply("can't find user for %s" % ievent.userhost)
1745
if ievent.channel[0] in '#!&':
1746
ievent.reply('%s: use a private message!' % (ievent.nick,))
1748
elif len(ievent.args) != 2:
1749
ievent.reply('identi-id <username> <password>')
1751
identiuser.add(user.name, ievent.args[0], ievent.args[1])
1754
cmnds.add('identi-id', handle_identi_id, 'USER')
1755
examples.add('identi-id', 'adds your identi.ca account', 'identi-id example secret')