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
)
53 """Obtains stream from pod.
55 request
= self
._connection
.get(self
._location
)
56 if request
.status_code
!= 200:
57 raise Exception('wrong status code: {0}'.format(request
.status_code
))
58 return [Post(str(post
['id']), self
._connection
) for post
in request
.json()]
61 """Removes all posts from stream.
66 """Removes all unexistent posts from stream.
69 for post
in self
._stream
:
77 if not deleted
: stream
.append(post
)
83 new_stream
= self
._obtain
()
84 ids
= [post
.post_id
for post
in self
._stream
]
87 for i
in range(len(new_stream
)):
88 if new_stream
[-i
].post_id
not in ids
:
89 stream
= [new_stream
[-i
]] + stream
90 ids
.append(new_stream
[-i
].post_id
)
95 """Fills the stream with posts.
97 self
._stream
= self
._obtain
()
99 def more(self
, max_time
=0):
100 """Tries to download more (older ones) Posts from Stream.
102 if not max_time
: max_time
= self
.max_time
- 3000000
103 self
.max_time
= max_time
104 params
= {'max_time': self
.max_time
}
105 request
= self
._connection
.get(self
._location
, params
=params
)
106 if request
.status_code
!= 200:
107 raise Exception('wrong status code: {0}'.format(request
.status_code
))
110 class Outer(Generic
):
111 """Object used by diaspy.models.User to represent
112 stream of other user.
115 """Obtains stream of other user.
117 request
= self
._connection
.get(self
._location
)
118 if request
.status_code
!= 200:
119 raise Exception('wrong status code: {0}'.format(request
.status_code
))
120 return [Post(str(post
['id']), self
._connection
) for post
in request
.json()]
123 class Stream(Generic
):
124 """The main stream containing the combined posts of the
125 followed users and tags and the community spotlights posts
126 if the user enabled those.
128 location
= 'stream.json'
130 def post(self
, text
='', aspect_ids
='public', photos
=None, photo
=''):
131 """This function sends a post to an aspect.
132 If both `photo` and `photos` are specified `photos` takes precedence.
134 :param text: Text to post.
136 :param aspect_ids: Aspect ids to send post to.
137 :type aspect_ids: str
138 :param photo: filename of photo to post
140 :param photos: id of photo to post (obtained from _photoupload())
143 :returns: diaspy.models.Post -- the Post which has been created
146 data
['aspect_ids'] = aspect_ids
147 data
['status_message'] = {'text': text
}
148 if photo
: data
['photos'] = self
._photoupload
(photo
)
149 if photos
: data
['photos'] = photos
151 request
= self
._connection
.post('status_messages',
152 data
=json
.dumps(data
),
153 headers
={'content-type': 'application/json',
154 'accept': 'application/json',
155 'x-csrf-token': self
._connection
.get_token()})
156 if request
.status_code
!= 201:
157 raise Exception('{0}: Post could not be posted.'.format(request
.status_code
))
159 post
= Post(str(request
.json()['id']), self
._connection
)
162 def _photoupload(self
, filename
):
163 """Uploads picture to the pod.
165 :param filename: path to picture file
168 :returns: id of the photo being uploaded
170 data
= open(filename
, 'rb')
175 params
['photo[pending]'] = 'true'
176 params
['set_profile_image'] = ''
177 params
['qqfile'] = filename
178 aspects
= self
._connection
.getUserInfo()['aspects']
179 for i
, aspect
in enumerate(aspects
):
180 params
['photo[aspect_ids][{0}]'.format(i
)] = aspect
['id']
182 headers
= {'content-type': 'application/octet-stream',
183 'x-csrf-token': self
._connection
.get_token(),
184 'x-file-name': filename
}
186 request
= self
._connection
.post('photos', data
=image
, params
=params
, headers
=headers
)
187 if request
.status_code
!= 200:
188 raise Exception('wrong error code: {0}'.format(request
.status_code
))
189 return request
.json()['data']['photo']['id']
192 class Activity(Stream
):
193 """Stream representing user's activity.
195 _location
= 'activity.json'
197 def _delid(self
, id):
198 """Deletes post with given id.
201 for p
in self
._stream
:
205 if post
is not None: post
.delete()
207 def delete(self
, post
):
208 """Deletes post from users activity.
209 `post` can be either post id or Post()
210 object which will be identified and deleted.
211 After deleting post the stream will be filled.
213 :param post: post identifier
214 :type post: str, diaspy.models.Post
216 if type(post
) == str: self
._delid
(post
)
217 elif type(post
) == Post
: post
.delete()
219 raise TypeError('this method accepts str or Post types: {0} given')
223 class Aspects(Generic
):
224 """This stream contains the posts filtered by
225 the specified aspect IDs. You can choose the aspect IDs with
226 the parameter `aspect_ids` which value should be
227 a comma seperated list of aspect IDs.
228 If the parameter is ommitted all aspects are assumed.
229 An example call would be `aspects.json?aspect_ids=23,5,42`
231 _location
= 'aspects.json'
233 def getAspectID(self
, aspect_name
):
234 """Returns id of an aspect of given name.
235 Returns -1 if aspect is not found.
237 :param aspect_name: aspect name (must be spelled exactly as when created)
238 :type aspect_name: str
242 aspects
= self
._connection
.getUserInfo()['aspects']
243 for aspect
in aspects
:
244 if aspect
['name'] == aspect_name
: id = aspect
['id']
247 def filterByIDs(self
, ids
):
248 self
._location
+= '?{0}'.format(','.join(ids
))
251 def add(self
, aspect_name
, visible
=0):
252 """This function adds a new aspect.
253 Status code 422 is accepted because it is returned by D* when
254 you try to add aspect already present on your aspect list.
256 :returns: Aspect() object of just created aspect
258 data
= {'authenticity_token': self
._connection
.get_token(),
259 'aspect[name]': aspect_name
,
260 'aspect[contacts_visible]': visible
}
262 request
= self
._connection
.post('aspects', data
=data
)
263 if request
.status_code
not in [200, 422]:
264 raise Exception('wrong status code: {0}'.format(request
.status_code
))
266 id = self
.getAspectID(aspect_name
)
267 return Aspect(self
._connection
, id)
269 def remove(self
, aspect_id
=-1, name
=''):
270 """This method removes an aspect.
271 You can give it either id or name of the aspect.
272 When both are specified, id takes precedence over name.
274 Status code 500 is accepted because although the D* will
275 go nuts it will remove the aspect anyway.
277 :param aspect_id: id fo aspect to remove
279 :param name: name of aspect to remove
282 if aspect_id
== -1 and name
: aspect_id
= self
.getAspectID(name
)
283 data
= {'_method': 'delete',
284 'authenticity_token': self
._connection
.get_token()}
285 request
= self
._connection
.post('aspects/{0}'.format(aspect_id
), data
=data
)
286 if request
.status_code
not in [200, 302, 500]:
287 raise Exception('wrong status code: {0}: cannot remove aspect'.format(request
.status_code
))
290 class Commented(Generic
):
291 """This stream contains all posts
292 the user has made a comment on.
294 _location
= 'commented.json'
297 class Liked(Generic
):
298 """This stream contains all posts the user liked.
300 _location
= 'liked.json'
303 class Mentions(Generic
):
304 """This stream contains all posts
305 the user is mentioned in.
307 _location
= 'mentions.json'
310 class FollowedTags(Generic
):
311 """This stream contains all posts
312 containing tags the user is following.
314 _location
= 'followed_tags.json'
316 def remove(self
, tag_id
):
317 """Stop following a tag.
319 :param tag_id: tag id
322 data
= {'authenticity_token': self
._connection
.get_token()}
323 request
= self
._connection
.delete('tag_followings/{0}'.format(tag_id
), data
=data
)
324 if request
.status_code
!= 404:
325 raise Exception('wrong status code: {0}'.format(request
.status_code
))
327 def add(self
, tag_name
):
329 Error code 403 is accepted because pods respod with it when request
330 is sent to follow a tag that a user already follows.
332 :param tag_name: tag name
334 :returns: int (response code)
336 data
= {'name': tag_name
,
337 'authenticity_token': self
._connection
.get_token(),
339 headers
= {'content-type': 'application/json',
340 'x-csrf-token': self
._connection
.get_token(),
341 'accept': 'application/json'
344 request
= self
._connection
.post('tag_followings', data
=json
.dumps(data
), headers
=headers
)
346 if request
.status_code
not in [201, 403]:
347 raise Exception('wrong error code: {0}'.format(request
.status_code
))
348 return request
.status_code
352 """This stream contains all posts containing a tag.
354 def __init__(self
, connection
, tag
):
356 :param connection: Connection() object
357 :type connection: diaspy.connection.Connection
361 self
._connection
= connection
362 self
._location
= 'tags/{0}'.format(tag
)