3 from diaspy
.models
import Post
, Aspect
5 """Docstrings for this module are taken from:
6 https://gist.github.com/MrZYX/01c93096c30dc44caf71
8 Documentation for D* JSON API taken from:
9 http://pad.spored.de/ro/r.qWmvhSZg7rk4OQam
14 """Object representing generic stream.
16 _location
= 'stream.json'
19 max_time
= int(time
.mktime(time
.gmtime()))
21 def __init__(self
, connection
, location
=''):
23 :param connection: Connection() object
24 :type connection: diaspy.connection.Connection
25 :param location: location of json (optional)
28 self
._connection
= connection
29 if location
: self
._location
= location
32 def __contains__(self
, post
):
33 """Returns True if stream contains given post.
35 return post
in self
._stream
38 """Provides iterable interface for stream.
40 return iter(self
._stream
)
42 def __getitem__(self
, n
):
43 """Returns n-th item in Stream.
45 return self
._stream
[n
]
48 """Returns length of the Stream.
50 return len(self
._stream
)
52 def _obtain(self
, max_time
=0):
53 """Obtains stream from pod.
56 if max_time
: params
['max_time'] = max_time
57 request
= self
._connection
.get(self
._location
, params
=params
)
58 if request
.status_code
!= 200:
59 raise error
.StreamError('wrong status code: {0}'.format(request
.status_code
))
60 return [Post(self
._connection
, post
['id']) for post
in request
.json()]
62 def _expand(self
, new_stream
):
63 """Appends older posts to stream.
65 ids
= [post
.post_id
for post
in self
._stream
]
67 for post
in new_stream
:
68 if post
.post_id
not in ids
:
70 ids
.append(post
.post_id
)
73 def _update(self
, new_stream
):
74 """Updates stream with new posts.
76 ids
= [post
.id for post
in self
._stream
]
79 for i
in range(len(new_stream
)):
80 if new_stream
[-i
].id not in ids
:
81 stream
= [new_stream
[-i
]] + stream
82 ids
.append(new_stream
[-i
].post_id
)
86 """Removes all posts from stream.
91 """Removes all unexistent posts from stream.
94 for post
in self
._stream
:
102 if not deleted
: stream
.append(post
)
103 self
._stream
= stream
108 self
._update
(self
._obtain
())
111 """Fills the stream with posts.
113 self
._stream
= self
._obtain
()
115 def more(self
, max_time
=0):
116 """Tries to download more (older ones) Posts from Stream.
118 :param max_time: seconds since epoch (optional, diaspy'll figure everything on its own)
121 if not max_time
: max_time
= self
.max_time
- 3000000
122 self
.max_time
= max_time
123 new_stream
= self
._obtain
(max_time
=max_time
)
124 self
._expand
(new_stream
)
127 class Outer(Generic
):
128 """Object used by diaspy.models.User to represent
129 stream of other user.
132 """Obtains stream of other user.
134 request
= self
._connection
.get(self
._location
)
135 if request
.status_code
!= 200:
136 raise error
.StreamError('wrong status code: {0}'.format(request
.status_code
))
137 return [Post(self
._connection
, post
['id']) for post
in request
.json()]
140 class Stream(Generic
):
141 """The main stream containing the combined posts of the
142 followed users and tags and the community spotlights posts
143 if the user enabled those.
145 location
= 'stream.json'
147 def post(self
, text
='', aspect_ids
='public', photos
=None, photo
=''):
148 """This function sends a post to an aspect.
149 If both `photo` and `photos` are specified `photos` takes precedence.
151 :param text: Text to post.
153 :param aspect_ids: Aspect ids to send post to.
154 :type aspect_ids: str
155 :param photo: filename of photo to post
157 :param photos: id of photo to post (obtained from _photoupload())
160 :returns: diaspy.models.Post -- the Post which has been created
163 data
['aspect_ids'] = aspect_ids
164 data
['status_message'] = {'text': text
}
165 if photo
: data
['photos'] = self
._photoupload
(photo
)
166 if photos
: data
['photos'] = photos
168 request
= self
._connection
.post('status_messages',
169 data
=json
.dumps(data
),
170 headers
={'content-type': 'application/json',
171 'accept': 'application/json',
172 'x-csrf-token': repr(self
._connection
)})
173 if request
.status_code
!= 201:
174 raise Exception('{0}: Post could not be posted.'.format(request
.status_code
))
176 post
= Post(self
._connection
, request
.json()['id'])
179 def _photoupload(self
, filename
):
180 """Uploads picture to the pod.
182 :param filename: path to picture file
185 :returns: id of the photo being uploaded
187 data
= open(filename
, 'rb')
192 params
['photo[pending]'] = 'true'
193 params
['set_profile_image'] = ''
194 params
['qqfile'] = filename
195 aspects
= self
._connection
.getUserInfo()['aspects']
196 for i
, aspect
in enumerate(aspects
):
197 params
['photo[aspect_ids][{0}]'.format(i
)] = aspect
['id']
199 headers
= {'content-type': 'application/octet-stream',
200 'x-csrf-token': repr(self
._connection
),
201 'x-file-name': filename
}
203 request
= self
._connection
.post('photos', data
=image
, params
=params
, headers
=headers
)
204 if request
.status_code
!= 200:
205 raise Exception('wrong error code: {0}'.format(request
.status_code
))
206 return request
.json()['data']['photo']['id']
209 class Activity(Stream
):
210 """Stream representing user's activity.
212 _location
= 'activity.json'
214 def _delid(self
, id):
215 """Deletes post with given id.
218 for p
in self
._stream
:
222 if post
is not None: post
.delete()
224 def delete(self
, post
):
225 """Deletes post from users activity.
226 `post` can be either post id or Post()
227 object which will be identified and deleted.
228 After deleting post the stream will be filled.
230 :param post: post identifier
231 :type post: str, diaspy.models.Post
233 if type(post
) == str: self
._delid
(post
)
234 elif type(post
) == Post
: post
.delete()
236 raise TypeError('this method accepts str or Post types: {0} given')
240 class Aspects(Generic
):
241 """This stream contains the posts filtered by
242 the specified aspect IDs. You can choose the aspect IDs with
243 the parameter `aspect_ids` which value should be
244 a comma seperated list of aspect IDs.
245 If the parameter is ommitted all aspects are assumed.
246 An example call would be `aspects.json?aspect_ids=23,5,42`
248 _location
= 'aspects.json'
250 def getAspectID(self
, aspect_name
):
251 """Returns id of an aspect of given name.
252 Returns -1 if aspect is not found.
254 :param aspect_name: aspect name (must be spelled exactly as when created)
255 :type aspect_name: str
259 aspects
= self
._connection
.getUserInfo()['aspects']
260 for aspect
in aspects
:
261 if aspect
['name'] == aspect_name
: id = aspect
['id']
264 def filterByIDs(self
, ids
):
265 self
._location
+= '?{0}'.format(','.join(ids
))
268 def add(self
, aspect_name
, visible
=0):
269 """This function adds a new aspect.
270 Status code 422 is accepted because it is returned by D* when
271 you try to add aspect already present on your aspect list.
273 :returns: Aspect() object of just created aspect
275 data
= {'authenticity_token': self
._connection
.get_token(),
276 'aspect[name]': aspect_name
,
277 'aspect[contacts_visible]': visible
}
279 request
= self
._connection
.post('aspects', data
=data
)
280 if request
.status_code
not in [200, 422]:
281 raise Exception('wrong status code: {0}'.format(request
.status_code
))
283 id = self
.getAspectID(aspect_name
)
284 return Aspect(self
._connection
, id)
286 def remove(self
, aspect_id
=-1, name
=''):
287 """This method removes an aspect.
288 You can give it either id or name of the aspect.
289 When both are specified, id takes precedence over name.
291 Status code 500 is accepted because although the D* will
292 go nuts it will remove the aspect anyway.
294 :param aspect_id: id fo aspect to remove
296 :param name: name of aspect to remove
299 if aspect_id
== -1 and name
: aspect_id
= self
.getAspectID(name
)
300 data
= {'_method': 'delete',
301 'authenticity_token': self
._connection
.get_token()}
302 request
= self
._connection
.post('aspects/{0}'.format(aspect_id
), data
=data
)
303 if request
.status_code
not in [200, 302, 500]:
304 raise Exception('wrong status code: {0}: cannot remove aspect'.format(request
.status_code
))
307 class Commented(Generic
):
308 """This stream contains all posts
309 the user has made a comment on.
311 _location
= 'commented.json'
314 class Liked(Generic
):
315 """This stream contains all posts the user liked.
317 _location
= 'liked.json'
320 class Mentions(Generic
):
321 """This stream contains all posts
322 the user is mentioned in.
324 _location
= 'mentions.json'
327 class FollowedTags(Generic
):
328 """This stream contains all posts
329 containing tags the user is following.
331 _location
= 'followed_tags.json'
333 def remove(self
, tag_id
):
334 """Stop following a tag.
336 :param tag_id: tag id
339 data
= {'authenticity_token': self
._connection
.get_token()}
340 request
= self
._connection
.delete('tag_followings/{0}'.format(tag_id
), data
=data
)
341 if request
.status_code
!= 404:
342 raise Exception('wrong status code: {0}'.format(request
.status_code
))
344 def add(self
, tag_name
):
346 Error code 403 is accepted because pods respod with it when request
347 is sent to follow a tag that a user already follows.
349 :param tag_name: tag name
351 :returns: int (response code)
353 data
= {'name': tag_name
,
354 'authenticity_token': self
._connection
.get_token(),
356 headers
= {'content-type': 'application/json',
357 'x-csrf-token': self
._connection
.get_token(),
358 'accept': 'application/json'
361 request
= self
._connection
.post('tag_followings', data
=json
.dumps(data
), headers
=headers
)
363 if request
.status_code
not in [201, 403]:
364 raise Exception('wrong error code: {0}'.format(request
.status_code
))
365 return request
.status_code
369 """This stream contains all posts containing a tag.
371 def __init__(self
, connection
, tag
):
373 :param connection: Connection() object
374 :type connection: diaspy.connection.Connection
378 self
._connection
= connection
379 self
._location
= 'tags/{0}'.format(tag
)