From b9d087bbe48c42f8a0cf408dbecf18157e61ae41 Mon Sep 17 00:00:00 2001 From: CYBERDEViLNL Date: Wed, 19 Dec 2018 00:25:33 +0100 Subject: [PATCH] * __upd__: Add `Comment()` to `diaspy.models.Post.comments` on `diaspy.models.Post.comment()` * __upd__: Delete `Comment()` from `diaspy.models.Post.comments` on `diaspy.models.Post.delete_comment()` * __upd__: Update `diaspy.models.Post._data["poll"]["participation_count"]` and `diaspy.models.Post._data["poll_participation_answer_id"]` after `diaspy.models.Post.vote_poll()` * __upd__: Update `diaspy.models.Post._data["participation"]` on `diaspy.models.Post.subscribe()` and `diaspy.models.Post.unsubscribe()` * __upd__: Removed `verify` keyword from `diaspy.connection.Connection` it's methods, it's now done through `**requestKwargs`. Default is still `True`. * __upd__: `diaspy.streams.Stream._photoupload()` to more public method `diaspy.streams.Stream.photoupload()` * __upd__: Update `diaspy.models.Notification.unread` and `diaspy.models.Notification._data['unread']` on `diasply.models.Notification.mark()` * __fix__: Don't update `diaspy.notifications.Notifications` it's `unread_count_by_type` and `unread_count` on `diaspy.notifications.Notifications._expand()` and `diaspy.notifications.Notifications._update()` since it's already done in `diaspy.notifications.Notifications._finalize()` * __new__: `diaspy.streams.Generic.deletePostGuid()` deletes `Post` with given `guid` from the local stream object. (If you manualy update a post and find that it's been deleted, call this with the `guid` of the deleted `Post`) * __new__: `diaspy.streams.Stream.deletephoto()` * __new__: `diaspy.notifications.Notifications.__len__()` * __new__: `diaspy.notifications.Notifications.data()` * __new__: `diaspy.models.Comments.delete()` deletes comment by `id`. * __new__: `diaspy.errors.NotificationError` Note: the current Camo check will be removed and replaced by nodeInfo soon. --- Changelog.markdown | 16 ++++++++++++++++ diaspy/connection.py | 16 +++++++++++----- diaspy/errors.py | 4 ++++ diaspy/models.py | 32 +++++++++++++++++++++++++++++--- diaspy/notifications.py | 15 ++++++--------- diaspy/streams.py | 35 ++++++++++++++++++++++++++++++++--- 6 files changed, 98 insertions(+), 20 deletions(-) diff --git a/Changelog.markdown b/Changelog.markdown index e7b0af1..70c584b 100644 --- a/Changelog.markdown +++ b/Changelog.markdown @@ -30,9 +30,19 @@ up-to-date than manual and if conflicts appear they should follow the order: #### Version `0.6.1.dev` (not final changelog for this version, still in development) * __upd__: `diaspy.models.Post.like()`, `diaspy.models.Post.delete_like()`, `diaspy.models.Post.reshare()` will now update data (count and likes/reshares) without doing another request. +* __upd__: Add `Comment()` to `diaspy.models.Post.comments` on `diaspy.models.Post.comment()` +* __upd__: Delete `Comment()` from `diaspy.models.Post.comments` on `diaspy.models.Post.delete_comment()` +* __upd__: Update `diaspy.models.Post._data["poll"]["participation_count"]` and `diaspy.models.Post._data["poll_participation_answer_id"]` after `diaspy.models.Post.vote_poll()` +* __upd__: Update `diaspy.models.Post._data["participation"]` on `diaspy.models.Post.subscribe()` and `diaspy.models.Post.unsubscribe()` +* __upd__: Removed `verify` keyword from `diaspy.connection.Connection` it's methods, it's now done through `**requestKwargs`. Default is still `True`. +* __upd__: `diaspy.streams.Stream._photoupload()` to more public method `diaspy.streams.Stream.photoupload()` +* __upd__: Update `diaspy.models.Notification.unread` and `diaspy.models.Notification._data['unread']` on `diasply.models.Notification.mark()` + * __fix__: `diaspy.models.Post.__init__()` checking on different fetch states was a mess. * __fix__: `diaspy.streams.Asepcts.filter()` location fix. +* __fix__: Don't update `diaspy.notifications.Notifications` it's `unread_count_by_type` and `unread_count` on `diaspy.notifications.Notifications._expand()` and `diaspy.notifications.Notifications._update()` since it's already done in `diaspy.notifications.Notifications._finalize()` + * __new__: `diaspy.tagFollowings.TagFollowings()` which represents the tags followed by the user. * __new__: `diaspy.models.FollowedTag()` which represents a tag followed by the user. It is used by `diaspy.tagFollowings.TagFollowings()`. @@ -42,6 +52,12 @@ up-to-date than manual and if conflicts appear they should follow the order: * __new__: `diaspy.streams.Public()` * __new__: `diaspy.models.Post.fetchlikes()`. * __new__: `diaspy.models.Post.fetchreshares()` +* __new__: `diaspy.streams.Generic.deletePostGuid()` deletes `Post` with given `guid` from the local stream object. (If you manualy update a post and find that it's been deleted, call this with the `guid` of the deleted `Post`) +* __new__: `diaspy.streams.Stream.deletephoto()` +* __new__: `diaspy.notifications.Notifications.__len__()` +* __new__: `diaspy.notifications.Notifications.data()` +* __new__: `diaspy.models.Comments.delete()` deletes comment by `id`. +* __new__: `diaspy.errors.NotificationError` * __rem__: `diaspy.streams.FollowedTags.get()` since it wasn’t doing anything usefull. diff --git a/diaspy/connection.py b/diaspy/connection.py index 24a6e95..2c91b7e 100644 --- a/diaspy/connection.py +++ b/diaspy/connection.py @@ -45,7 +45,9 @@ class Connection(): self._token = '' self._diaspora_session = '' self._fetch_token_from = 'stream' - self._requests_kwargs = requestsKwargs + self._requests_kwargs = {'verify':self._verify_SSL} + if requestsKwargs: self._requests_kwargs.update(requestsKwargs) + self._camo_enabled = False try: self._setlogin(username, password) except requests.exceptions.MissingSchema: @@ -98,7 +100,7 @@ class Connection(): if not direct: url = '{0}/{1}'.format(self.pod, string) else: url = string if not kwargs: kwargs = self._requests_kwargs - return self._session.get(url, params=params, headers=headers, verify=self._verify_SSL, **kwargs) + return self._session.get(url, params=params, headers=headers, **kwargs) def tokenFrom(self, location): """Sets location for the *next* fetch of CSRF token. @@ -131,7 +133,7 @@ class Connection(): if 'X-CSRF-Token' not in headers: headers['X-CSRF-Token'] = self.get_token() if not kwargs: kwargs = self._requests_kwargs - request = self._session.post(string, data, headers=headers, params=params, verify=self._verify_SSL, **kwargs) + request = self._session.post(string, data, headers=headers, params=params, **kwargs) return request def put(self, string, data=None, headers={}, params={}, **kwargs): @@ -142,7 +144,7 @@ class Connection(): headers['X-CSRF-Token'] = self.get_token() if not kwargs: kwargs = self._requests_kwargs if data is not None: request = self._session.put(string, data, headers=headers, params=params, **kwargs) - else: request = self._session.put(string, headers=headers, params=params, verify=self._verify_SSL, **kwargs) + else: request = self._session.put(string, headers=headers, params=params, **kwargs) return request def delete(self, string, data = None, headers={}, **kwargs): @@ -159,7 +161,7 @@ class Connection(): if 'X-CSRF-Token' not in headers: headers['X-CSRF-Token'] = self.get_token() if not kwargs: kwargs = self._requests_kwargs - request = self._session.delete(string, data=data, headers=headers, verify=self._verify_SSL, **kwargs) + request = self._session.delete(string, data=data, headers=headers, **kwargs) return request def _checkCamo(self): @@ -208,6 +210,9 @@ class Connection(): """ self.get('users/sign_out') self.token = '' + self._userdata = {} + self._diaspora_session = '' + self._camo_enabled = False def podswitch(self, pod, username, password, login=True): """Switches pod from current to another one. @@ -272,3 +277,4 @@ class Connection(): """Sets whether there should be an error if a SSL-Certificate could not be verified. """ self._verify_SSL = verify + self._requests_kwargs.update({'verify':verify}) diff --git a/diaspy/errors.py b/diaspy/errors.py index 48d754a..5aa612a 100644 --- a/diaspy/errors.py +++ b/diaspy/errors.py @@ -72,6 +72,10 @@ class SearchError(DiaspyError): """ pass +class NotificationError(DiaspyError): + """Exception raised when something related to notifications goes wrong. + """ + pass class ConversationError(DiaspyError): """Exception raised when something related to conversations goes wrong. diff --git a/diaspy/models.py b/diaspy/models.py index d256c24..e06f8dc 100644 --- a/diaspy/models.py +++ b/diaspy/models.py @@ -212,8 +212,11 @@ class Notification(): """ headers = {'x-csrf-token': repr(self._connection)} params = {'set_unread': json.dumps(unread)} - self._connection.put('notifications/{0}'.format(self['id']), params=params, headers=headers) + response = self._connection.put('notifications/{0}'.format(self['id']), params=params, headers=headers) + if response.status_code != 200: + raise errors.NotificationError('Cannot mark notification: {0}'.format(response.status_code)) self._data['unread'] = unread + self.unread = unread class Conversation(): @@ -450,6 +453,12 @@ class Comments(): def ids(self): return [c.id for c in self._comments] + def delete(self, comment_id): + for index, comment in enumerate(self._comments): + if comment.id == comment_id: + self._comments.pop(index); + break; + def add(self, comment): """ Expects Comment() object @@ -633,7 +642,9 @@ class Post(): if request.status_code != 201: raise Exception('{0}: Comment could not be posted.' .format(request.status_code)) - return Comment(request.json()) + comment = Comment(request.json()) + self.comments.add(comment); + return comment def vote_poll(self, poll_answer_id): """This function votes on a post's poll @@ -652,7 +663,16 @@ class Post(): if request.status_code != 201: raise Exception('{0}: Vote on poll failed.' .format(request.status_code)) - return request.json() + + data = request.json() + self._data["poll"]["participation_count"] += 1 + self._data["poll_participation_answer_id"] = data["poll_participation"]["poll_answer_id"] + + for answer in self._data["poll"]["poll_answers"]: + if answer["id"] == poll_answer_id: + answer["vote_count"] +=1; + break; + return data def hide(self): """ @@ -693,6 +713,8 @@ class Post(): raise Exception('{0}: Failed to subscribe to post' .format(request.status_code)) + self._data.update({"participation" : True}) + def unsubscribe(self): """ -> POST /posts/123/participation HTTP/1.1 @@ -707,6 +729,8 @@ class Post(): raise Exception('{0}: Failed to unsubscribe to post' .format(request.status_code)) + self._data.update({"participation" : False}) + def report(self): """ TODO @@ -739,6 +763,8 @@ class Post(): raise errors.PostError('{0}: Comment could not be deleted' .format(request.status_code)) + self.comments.delete(comment_id) + def delete_like(self): """This function removes a like from a post """ diff --git a/diaspy/notifications.py b/diaspy/notifications.py index cb5b42f..877c80f 100644 --- a/diaspy/notifications.py +++ b/diaspy/notifications.py @@ -19,6 +19,8 @@ class Notifications(): self._notifications = self.get() self.page = 1 + def __len__(self): return len(self._notifications); + def __iter__(self): return iter(self._notifications) @@ -30,6 +32,8 @@ class Notifications(): self._data['unread_count_by_type'] = notifications['unread_count_by_type'] return [Notification(self._connection, n) for n in notifications.get('notification_list', [])] + def data(self): return self._data; + def last(self): """Returns list of most recent notifications. """ @@ -48,13 +52,10 @@ class Notifications(): data = self._data for n in new_notifications: if n.id not in ids: - if n.unread: - data['unread_count'] +=1 - data['unread_count_by_type'][n.type] +=1 notifications.append(n) ids.append(n.id) self._notifications = notifications - self._data = data + self._data.update(data); def _update(self, new_notifications): ids = [notification.id for notification in self._notifications] @@ -67,9 +68,6 @@ class Notifications(): for i in range(len(new_notifications)): if new_notifications[-i].id not in ids: - if new_notifications[-i].unread: - data['unread_count'] +=1 - data['unread_count_by_type'][new_notifications[-i].type] +=1 notifications = [new_notifications[-i]] + notifications ids.append(new_notifications[-i].id) self._notifications = notifications @@ -84,8 +82,7 @@ class Notifications(): if not page: page = self.page + 1 self.page = page result = self.get(per_page=per_page, page=page) - if result: - self._expand( result ) + if result: self._expand( result ) def get(self, per_page=5, page=1): """Returns list of notifications. diff --git a/diaspy/streams.py b/diaspy/streams.py index c76946d..2593b22 100644 --- a/diaspy/streams.py +++ b/diaspy/streams.py @@ -131,6 +131,21 @@ class Generic(): """ self._stream = [] + def deletePostGuid(self, guid): + """Deleted post from stream by guid + + :param guid: guid of the post to delete + :type guid: str + + NOTE: This affects local object only! So no request will be made to a + pod. This can be used if you tried to update a Post() and it failed then + you know the post is deleted. + """ + for index, post in enumerate(self._stream): + if post.guid == guid: + self._stream.pop(index); + break; + def purge(self): """Removes all unexistent posts from stream. """ @@ -273,7 +288,7 @@ class Stream(Generic): :param photo: filename of photo to post :type photo: str - :param photos: id of photo to post (obtained from _photoupload()) + :param photos: id of photo to post (obtained from photoupload()) :type photos: int :param provider_display_name: name of provider displayed under the post @@ -294,7 +309,7 @@ class Stream(Generic): data['aspect_ids'] = aspect_ids data['status_message'] = ({'text': text, 'provider_display_name': provider_display_name}) - if photo: data['photos'] = self._photoupload(photo) + if photo: data['photos'] = self.photoupload(photo) if photos: data['photos'] = photos if poll_question and poll_answers: data['poll_question'] = poll_question @@ -312,9 +327,23 @@ class Stream(Generic): post_json = request.json() post = Post(self._connection, id=post_json['id'], guid=post_json['guid'], post_data=post_json) + self._stream.insert(0, post); return post - def _photoupload(self, filename, aspects=[]): + def deletephoto(self, id): + """Remove photo from the pod (if decided not needed anymore). + + :param id: photo id to delete + :type id: int + """ + data = {'authenticity_token': repr(self._connection)} + request = self._connection.delete('photos/{0}'.format(id), + data=data, + headers={'accept': 'application/json'}) + if request.status_code != 204: + raise errors.StreamError('{0}: Photo could not be deleted'.format(request.status_code)) + + def photoupload(self, filename, aspects=[]): """Uploads picture to the pod. :param filename: path to picture file -- 2.25.1