From 0c28bf0b27aff3d23f6308e0936f765e0582b924 Mon Sep 17 00:00:00 2001 From: CYBERDEViLNL Date: Tue, 8 May 2018 21:34:03 +0200 Subject: [PATCH] IMPORTANT: these changes will require dateutil or pytz Please review, I hope the other devs will approve to these changes. connection.py - Set self._userdata on getUserData(). errors.py - Added SearchError and TagError. models.py - Added removeAspect() to class Aspect(). - Set self.id and self.guid on Notification() class. - Created class Comments() instead of the list. - Post() can now be set with post data from a response without fetching the post again. - Post() now returns a Comment() object instead of json when commented. - Update Post()'s self._data['interactions']['likes'] after liked. - Added some interaction functions for class Post(): vote_poll( poll_answer_id ), hide(), mute(), subscribe(), unsubscribe(). -- report() is still TODO notifications.py - I made some mistake again with updating the notification values, so hopefully fixed now. people.py - Able to manually set User()'s data. - Remove aspect id from User() object when removed. - Made User()'s handle() little more flexible and replaced self['handle'] with self.handle(). - Added getPhotos() and deletePhoto() functions to User() class. - getHCard() didn't seem so work anymore so tweaked it a little, should be good now. - Class Contacts() it's now optional to set fetch param. - Contacts() now has functionality to add and delete Aspects. - Contacts()'s get() should now return all contacts if param `set` is set. streams.py - Fetching more posts seemed to miss some posts, hoped it's fixed now ( But it will require dateutil or pytz ). - Posts where fetched twice. - Removed _obtain() from Outer(). - Updated Aspect()'s filter function. --- diaspy/connection.py | 6 +- diaspy/errors.py | 9 ++ diaspy/models.py | 174 +++++++++++++++++++++++++++++++-- diaspy/notifications.py | 22 ++--- diaspy/people.py | 211 ++++++++++++++++++++++++++++++++++++---- diaspy/streams.py | 84 ++++++++++++---- 6 files changed, 445 insertions(+), 61 deletions(-) diff --git a/diaspy/connection.py b/diaspy/connection.py index 8f592ff..17d92d9 100644 --- a/diaspy/connection.py +++ b/diaspy/connection.py @@ -235,6 +235,9 @@ class Connection(): """ return self._diaspora_session + def userdata(self): + return self._userdata + def getUserData(self): """Returns user data. """ @@ -243,7 +246,8 @@ class Connection(): if userdata is None: userdata = self._userinfo_regex_2.search(request.text) if userdata is None: raise errors.DiaspyError('cannot find user data') userdata = userdata.group(1) - return json.loads(userdata) + self._userdata = json.loads(userdata) + return self._userdata def set_verify_SSL(self, verify): """Sets whether there should be an error if a SSL-Certificate could not be verified. diff --git a/diaspy/errors.py b/diaspy/errors.py index 08e5f5a..d28aa73 100644 --- a/diaspy/errors.py +++ b/diaspy/errors.py @@ -105,6 +105,15 @@ class SettingsError(DiaspyError): """ pass +class SearchError(DiaspyError): + """Exception raised when something related to searching goes wrong. + """ + pass + +class TagError(DiaspyError): + """Exception raised when something related to settings goes wrong. + """ + pass def react(r, message='', accepted=[200, 201, 202, 203, 204, 205, 206], exception=DiaspyError): """This method tries to decide how to react diff --git a/diaspy/models.py b/diaspy/models.py index 910ab24..d6281dd 100644 --- a/diaspy/models.py +++ b/diaspy/models.py @@ -11,7 +11,6 @@ import re from diaspy import errors - class Aspect(): """This class represents an aspect. @@ -32,19 +31,36 @@ class Aspect(): self._cached = request.json() return self._cached + def removeAspect(self): + """ + --> POST /aspects/{id} HTTP/1.1 + --> _method=delete&authenticity_token={token} + + <-- HTTP/1.1 302 Found + + TODO: status_codes + + Removes whole aspect. + :returns: None + """ + request = self._connection.tokenFrom('contacts').delete('aspects/{}'.format(self.id)) + + if request.status_code != 302: + raise errors.AspectError('wrong status code: {0}'.format(request.status_code)) + def addUser(self, user_id): """Add user to current aspect. :param user_id: user to add to aspect :type user_id: int :returns: JSON from request - + --> POST /aspect_memberships HTTP/1.1 --> Accept: application/json, text/javascript, */*; q=0.01 --> Content-Type: application/json; charset=UTF-8 --> {"aspect_id":123,"person_id":123} - + <-- HTTP/1.1 200 OK """ data = { @@ -78,6 +94,8 @@ class Aspect(): if response is None: raise errors.CSRFProtectionKickedIn() + # Now you should fetchguid(fetch_stream=False) on User to update aspect membership_id's + # Or update it locally with the response return response def removeUser(self, user): @@ -87,10 +105,12 @@ class Aspect(): :type user: diaspy.people.User object """ membership_id = None + to_remove = None for each in user.aspectMemberships(): print(self.id, each) if each.get('aspect', {}).get('id') == self.id: membership_id = each.get('id') + to_remove = each break # no need to continue if membership_id is None: @@ -104,6 +124,10 @@ class Aspect(): elif request.status_code != 200: raise errors.AspectError('cannot remove user from aspect: {0}'.format(request.status_code)) + if 'contact' in user.data: # User object + if to_remove: user.data['contact']['aspect_memberships'].remove( to_remove ) # remove local aspect membership_id + else: # User object from Contacts() + if to_remove: user.data['aspect_memberships'].remove( to_remove ) # remove local aspect membership_id return request.json() @@ -248,6 +272,8 @@ class Comment(): """ def __init__(self, data): self._data = data + self.id = data['id'] + self.guid = data['guid'] def __str__(self): """Returns comment's text. @@ -270,6 +296,46 @@ class Comment(): """ return self._data['author'][key] +class Comments(): + def __init__(self, comments=None): + self._comments = comments + + def __iter__(self): + if self._comments: + for comment in self._comments: + yield comment + + def __len__(self): + if self._comments: + return len(self._comments) + + def __getitem__(self, index): + if self._comments: + return self._comments[index] + + def __bool__(self): + if self._comments: + return True + return False + + def ids(self): + return [c.id for c in self._comments] + + def add(self, comment): + """ Expects comment object + TODO self._comments is None sometimes, have to look into it.""" + if comment and self._comments: + self._comments.append(comment) + + def set(self, comments): + """Sets comments wich already have a Comment obj""" + if comments: + self._comments = comments + + def set_json(self, json_comments): + """Sets comments for this post from post data.""" + if json_comments: + self._comments = [Comment(c) for c in json_comments] class Post(): """This class represents a post. @@ -277,7 +343,7 @@ class Post(): .. note:: Remember that you need to have access to the post. """ - def __init__(self, connection, id=0, guid='', fetch=True, comments=True): + def __init__(self, connection, id=0, guid='', fetch=True, comments=True, post_data=None): """ :param id: id of the post (GUID is recommended) :type id: int @@ -289,17 +355,25 @@ class Post(): :type fetch: bool :param comments: defines whether to fetch post's comments or not (if True also data will be fetched) :type comments: bool + :param post_data: contains post data so no need to fetch the post if this is set, until you want to update post data + :type: json """ if not (guid or id): raise TypeError('neither guid nor id was provided') self._connection = connection self.id = id self.guid = guid self._data = {} - self.comments = [] + self.comments = Comments() + if post_data: + self._data = post_data + if fetch: self._fetchdata() if comments: if not self._data: self._fetchdata() self._fetchcomments() + else: + if not self._data: self._fetchdata() + self.comments.set_json( self['interactions']['comments'] ) def __repr__(self): """Returns string containing more information then str(). @@ -333,7 +407,7 @@ class Post(): request = self._connection.get('posts/{0}.json'.format(id)) if request.status_code != 200: raise errors.PostError('{0}: could not fetch data for post: {1}'.format(request.status_code, id)) - else: + elif request: self._data = request.json() return self['guid'] @@ -348,7 +422,7 @@ class Post(): if request.status_code != 200: raise errors.PostError('{0}: could not fetch comments for post: {1}'.format(request.status_code, id)) else: - self.comments = [Comment(c) for c in request.json()] + self.comments.set([Comment(c) for c in request.json()]) def update(self): """Updates post data. @@ -380,14 +454,18 @@ class Post(): """ data = {'authenticity_token': repr(self._connection)} - request = self._connection.post('posts/{0}/likes'.format(self.id), + request = self._connection.post('posts/{0}/likes'.format(self.id), data=data, headers={'accept': 'application/json'}) if request.status_code != 201: raise errors.PostError('{0}: Post could not be liked.' .format(request.status_code)) - return request.json() + + likes_json = request.json() + if likes_json: + self._data['interactions']['likes'] = [likes_json] + return likes_json def reshare(self): """This function reshares a post @@ -417,8 +495,86 @@ class Post(): if request.status_code != 201: raise Exception('{0}: Comment could not be posted.' .format(request.status_code)) + return Comment(request.json()) + + def vote_poll(self, poll_answer_id): + """This function votes on a post's poll + + :param poll_answer_id: id to poll vote. + :type poll_answer_id: int + """ + poll_id = self._data['poll']['poll_id'] + data = {'poll_answer_id': poll_answer_id, + 'poll_id': poll_id, + 'post_id': self.id, + 'authenticity_token': repr(self._connection)} + request = self._connection.post('posts/{0}/poll_participations'.format(self.id), + data=data, + headers={'accept': 'application/json'}) + if request.status_code != 201: + raise Exception('{0}: Vote on poll failed.' + .format(request.status_code)) return request.json() + def hide(self): + """ + -> PUT /share_visibilities/42 HTTP/1.1 + post_id=123 + <- HTTP/1.1 200 OK + """ + headers = {'x-csrf-token': repr(self._connection)} + params = {'post_id': json.dumps(self.id)} + request = self._connection.put('share_visibilities/42', params=params, headers=headers) + if request.status_code != 200: + raise Exception('{0}: Failed to hide post.' + .format(request.status_code)) + + def mute(self): + """ + -> POST /blocks HTTP/1.1 + {"block":{"person_id":123}} + <- HTTP/1.1 204 No Content + """ + headers = {'content-type':'application/json', 'x-csrf-token': repr(self._connection)} + data = json.dumps({ 'block': { 'person_id' : self._data['author']['id'] } }) + request = self._connection.post('blocks', data=data, headers=headers) + if request.status_code != 204: + raise Exception('{0}: Failed to block person' + .format(request.status_code)) + + def subscribe(self): + """ + -> POST /posts/123/participation HTTP/1.1 + <- HTTP/1.1 201 Created + """ + headers = {'x-csrf-token': repr(self._connection)} + data = {} + request = self._connection.post('posts/{}/participation' + .format( self.id ), data=data, headers=headers) + if request.status_code != 201: + raise Exception('{0}: Failed to subscribe to post' + .format(request.status_code)) + + def unsubscribe(self): + """ + -> POST /posts/123/participation HTTP/1.1 + _method=delete + <- HTTP/1.1 200 OK + """ + headers = {'x-csrf-token': repr(self._connection)} + data = { "_method": "delete" } + request = self._connection.post('posts/{}/participation' + .format( self.id ), headers=headers, data=data) + if request.status_code != 200: + raise Exception('{0}: Failed to unsubscribe to post' + .format(request.status_code)) + + def report(self): + """ + TODO + """ + pass + def delete(self): """ This function deletes this post """ diff --git a/diaspy/notifications.py b/diaspy/notifications.py index d3ef645..1fbd44f 100644 --- a/diaspy/notifications.py +++ b/diaspy/notifications.py @@ -50,38 +50,36 @@ class Notifications(): if n.id not in ids: if n.unread: data['unread_count'] +=1 - data['unread_count_by_type'][n.type] +=1 + data['unread_count_by_type'][n.type] +=1 notifications.append(n) ids.append(n.id) self._notifications = notifications self._data = data - + def _update(self, new_notifications): ids = [notification.id for notification in self._notifications] notifications = self._notifications data = self._data - + update = False - if new_notifications[len(new_notifications)].id not in ids: + if new_notifications[len(new_notifications)-1].id not in ids: update = True - + for i in range(len(new_notifications)): if new_notifications[-i].id not in ids: if new_notifications[-i].unread: - data[new_notifications[-i].type].unread_count +=1 - data[new_notifications[-i].type].unread_count_by_type +=1 + 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 self._data = data - if update: - self.update() # if there is a gap + if update: self.update() # if there is a gap def update(self, per_page=5, page=1): result = self.get(per_page=per_page, page=page) - if result: - self._expand( result ) - + if result: self._update( result ) + def more(self, per_page=5, page=0): if not page: page = self.page + 1 self.page = page diff --git a/diaspy/people.py b/diaspy/people.py index 3d618d7..5a88386 100644 --- a/diaspy/people.py +++ b/diaspy/people.py @@ -3,6 +3,7 @@ import json import re import warnings +import time from diaspy.streams import Outer from diaspy.models import Aspect @@ -60,9 +61,9 @@ class User(): if person_id is None: raise errors.KeyMissingFromFetchedData('id', person) - return User(connection, guid, handle, id) + return User(connection, guid, handle, id, data=data) - def __init__(self, connection, guid='', handle='', fetch='posts', id=0): + def __init__(self, connection, guid='', handle='', fetch='posts', id=0, data=None): self._connection = connection self.stream = [] self.data = { @@ -70,7 +71,9 @@ class User(): 'handle': handle, 'id': id, } - self._fetch(fetch) + self.photos = [] + if data: self.data.update( data ) + if fetch: self._fetch(fetch) def __getitem__(self, key): return self.data[key] @@ -82,6 +85,7 @@ class User(): return '{0} ({1})'.format(self.handle(), self.guid()) def handle(self): + if 'handle' in self.data: return self['handle'] return self.data.get('diaspora_id', 'Unknown handle') def guid(self): @@ -97,7 +101,7 @@ class User(): """Fetch user posts or data. """ if fetch == 'posts': - if self['handle'] and not self['guid']: self.fetchhandle() + if self.handle() and not self['guid']: self.fetchhandle() else: self.fetchguid() elif fetch == 'data' and self['handle']: self.fetchprofile() @@ -139,23 +143,89 @@ class User(): def fetchprofile(self): """Fetches user data. - """ - data = search.Search(self._connection).user(self['handle']) + """ + data = search.Search(self._connection).user(self.handle()) if not data: - raise errors.UserError('user with handle "{0}" has not been found on pod "{1}"'.format(self['handle'], self._connection.pod)) + raise errors.UserError('user with handle "{0}" has not been found on pod "{1}"'.format(self.handle(), self._connection.pod)) else: - self.data = data[0] + self.data.update( data[0] ) def aspectMemberships(self): - return self.data.get('contact', {}).get('aspect_memberships', []) + if 'contact' in self.data: + return self.data.get('contact', {}).get('aspect_memberships', []) + else: + return self.data.get('aspect_memberships', []) + + def getPhotos(self): + """ + --> GET /people/{GUID}/photos.json HTTP/1.1 + + <-- HTTP/1.1 200 OK + + { + "photos":[ + { + "id":{photo_id}, + "guid":"{photo_guid}", + "created_at":"2018-03-08T23:48:31.000Z", + "author":{ + "id":{author_id}, + "guid":"{author_guid}", + "name":"{author_name}", + "diaspora_id":"{diaspora_id}", + "avatar":{"small":"{avatar_url_small}","medium":"{avatar_url_medium}","large":"{avatar_url_large}"} + }, + "sizes":{ + "small":"{photo_url}", + "medium":"{photo_url}", + "large":"{photo_url}" + }, + "dimensions":{"height":847,"width":998}, + "status_message":{ + "id":{post_id} + } + },{ .. + } + + if there are no photo's it returns: + {"photos":[]} + """ + + request = self._connection.get('/people/{0}/photos.json'.format(self['guid'])) + if request.status_code != 200: raise errors.UserError('could not fetch photos for user: {0}'.format(self['guid'])) + + json = request.json() + if json: self.photos = json['photos'] + return json['photos'] def getHCard(self): - """Returns XML string containing user HCard. + """Returns json containing user HCard. + --> /people/{guid}/hovercard.json?_={timestamp} + + <-- HTTP/2.0 200 OK + { + "id":123, + "guid":"1234567890abcdef", + "name":"test", + "diaspora_id":"batman@test.test", + "contact":false, + "profile":{ + "avatar":"https://nicetesturl.url/image.jpg", + "tags":["tag1", "tag2", "tag3", "tag4", "tag5"]} + } """ - request = self._connection.get('hcard/users/{0}'.format(self['guid'])) + timestamp = int(time.mktime(time.gmtime())) + request = self._connection.get('/people/{0}/hovercard.json?_={}'.format(self['guid'], timestamp)) if request.status_code != 200: raise errors.UserError('could not fetch hcard for user: {0}'.format(self['guid'])) - return request.text + return request.json() + def deletePhoto(self, photo_id): + """ + --> DELETE /photos/{PHOTO_ID} HTTP/1.1 + <-- HTTP/1.1 204 No Content + """ + request = self._connection.delete('/photos/{0}'.format(photo_id)) + if request.status_code != 204: raise errors.UserError('could not delete photo_id: {0}'.format(photo_id)) class Me(): """Object represetnting current user. @@ -182,30 +252,95 @@ class Me(): class Contacts(): """This class represents user's list of contacts. """ - def __init__(self, connection): + def __init__(self, connection, fetch=False, set=''): self._connection = connection + self.contacts = None + if fetch: self.contacts = self.get(set) + + def __getitem__(self, index): + return self.contacts[index] + + def addAspect(self, name, visible=False): + """ + --> POST /aspects HTTP/1.1 + --> {"person_id":null,"name":"test","contacts_visible":false} + + <-- HTTP/1.1 200 OK + + Add new aspect. + + TODO: status_code's + + :param name: aspect name to add + :type name: str + :param visible: sets if contacts in aspect are visible for each and other + :type visible: bool + :returns: JSON from request + """ + data = { + 'person_id': None, + 'name': name, + 'contacts_visible': visible + } + headers={'content-type': 'application/json', + 'accept': 'application/json' } + request = self._connection.tokenFrom('contacts').post('aspects', headers=headers, data=json.dumps(data)) + + if request.status_code == 400: + raise errors.AspectError('duplicate record, aspect alreadt exists: {0}'.format(request.status_code)) + elif request.status_code != 200: + raise errors.AspectError('wrong status code: {0}'.format(request.status_code)) + + new_aspect = request.json() + self._connection.userdata()['aspects'].append( new_aspect ) + + return new_aspect + + def deleteAspect(self, aspect_id): + """ + --> POST /aspects/{ASPECT_ID} HTTP/1.1 + _method=delete&authenticity_token={TOKEN} + Content-Type: application/x-www-form-urlencoded + + <-- HTTP/1.1 302 Found + Content-Type: text/html; charset=utf-8 + """ + request = self._connection.tokenFrom('contacts').delete('aspects/{}'.format( aspect_id )) + + if request.status_code != 200: # since we don't post but delete + raise errors.AspectError('wrong status code: {0}'.format(request.status_code)) def add(self, user_id, aspect_ids): """Add user to aspects of given ids. - :param user_id: user guid + :param user_id: user id (not guid) :type user_id: str :param aspect_ids: list of aspect ids :type aspect_ids: list """ - for aid in aspect_ids: Aspect(self._connection, aid).addUser(user_id) + # TODO update self.contacts + # Returns {"aspect_id":123,"person_id":123} + for aid in aspect_ids: + new_aspect_membership = Aspect(self._connection, aid).addUser(user_id) + + # user. + if new_aspect_membership: + for user in self.contacts: + if int(user.data['person_id']) == int(user_id): + user.data['aspect_memberships'].append( new_aspect_membership ) + return new_aspect_membership def remove(self, user_id, aspect_ids): """Remove user from aspects of given ids. - :param user_id: user guid + :param user_id: user id :type user_id: str :param aspect_ids: list of aspect ids :type aspect_ids: list """ for aid in aspect_ids: Aspect(self._connection, aid).removeUser(user_id) - def get(self, set=''): + def get(self, set='', page=0): """Returns list of user contacts. Contact is a User() who is in one or more of user's aspects. @@ -219,13 +354,51 @@ class Contacts(): If `set` is `only_sharing` it will return users who are only sharing with logged user and ARE NOT in his/hers aspects. + # On "All contacts" button diaspora + on the time of testing this I had 20 contacts and 10 that + where only sharing with me. So 30 in total. + + --> GET /contacts?set=all HTTP/1.1 + <-- HTTP/1.1 200 OK + returned 25 contacts (5 only sharing with me) + + --> GET /contacts.json?page=1&set=all&_=1524410225376 HTTP/1.1 + <-- HTTP/1.1 200 OK + returned the same list as before. + + --> GET /contacts.json?page=2&set=all&_=1524410225377 HTTP/1.1 + <-- HTTP/1.1 200 OK + returned the other 5 that where only sharing with me. + + --> GET /contacts.json?page=3&set=all&_=1524410225378 HTTP/1.1 + <-- HTTP/1.1 200 OK + returned empty list. + + It appears that /contacts?set=all returns a maximum of 25 + contacts. + + So if /contacts?set=all returns 25 contacts then request next + page until page returns a list with less then 25. I don't see a + reason why we should request page=1 'cause the previous request + will be the same. So begin with page=2 if /contacts?set=all + returns 25. + :param set: if passed could be 'all' or 'only_sharing' :type set: str """ params = {} - if set: params['set'] = set + if set: + params['set'] = set + params['_'] = int(time.mktime(time.gmtime())) + if page: params['page'] = page request = self._connection.get('contacts.json', params=params) if request.status_code != 200: raise Exception('status code {0}: cannot get contacts'.format(request.status_code)) - return [User.parse(self._connection, each) for each in request.json()] + + json = request.json() + users = [User.parse(self._connection, each) for each in json] + if len(json) == 25: + if not page: page = 1 + users += self.get(set=set, page=page+1) + return users diff --git a/diaspy/streams.py b/diaspy/streams.py index a5e5ca5..c9ca429 100644 --- a/diaspy/streams.py +++ b/diaspy/streams.py @@ -11,6 +11,29 @@ import time from diaspy.models import Post, Aspect from diaspy import errors +""" +Remember created_at is in UTC so I found two options to +convert/parse it to UTC timestamp: dateutil or pytz (found some +more but those libs aren't in default repo of main distro's) + +We need this to get a UTC timestamp from the latest loaded post in the +stream, so we can use it for the more() function. +""" +try: + import dateutil.parser + def parse_utc_timestamp(date_str): + return round(dateutil.parser.parse(date_str).timestamp()) + +except ImportError: + try: + from datetime import datetime + from pytz import timezone + def parse_utc_timestamp(date_str): + return round(datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone('UTC')).timestamp()) + + except ImportError: + print("Please install either python-dateutil or python-pytz") + exit # TODO raise exception class Generic(): """Object representing generic stream. @@ -28,6 +51,7 @@ class Generic(): """ self._connection = connection if location: self._location = location + self.latest = None self._stream = [] # since epoch self.max_time = int(time.mktime(time.gmtime())) @@ -60,18 +84,29 @@ class Generic(): """ params = {} if max_time: + if self.latest == None: + self.latest = int(time.mktime(time.gmtime()) * 1000) + self.latest -= max_time + else: self.latest += 1 params['max_time'] = max_time - params['_'] = int(time.time() * 1000) + params['_'] = self.latest + print("Diaspy _obtain.params: {}".format(params)) request = self._connection.get(self._location, params=params) if request.status_code != 200: raise errors.StreamError('wrong status code: {0}'.format(request.status_code)) posts = [] + latest_time = None # Used to get the created_at from the latest posts we received. for post in request.json(): try: - posts.append(Post(self._connection, id=post['id'], guid=post['guid'])) + comments = False + if post['interactions']['comments_count'] > 3: comments = True + posts.append(Post(self._connection, id=post['id'], guid=post['guid'], fetch=False, comments=comments, post_data=post)) + if post['created_at']: latest_time = post['created_at'] except errors.PostError: if not suppress: raise + if latest_time: + self.max_time = parse_utc_timestamp( latest_time ) return posts def _expand(self, new_stream): @@ -133,12 +168,16 @@ class Generic(): def more(self, max_time=0, backtime=86400): """Tries to download more (older posts) posts from Stream. + TODO backtime isn't used anymore. + Diaspora reference: https://github.com/diaspora/diaspora/blob/26a9e50ef935628c800f9a21d345057556fa5c31/app/helpers/stream_helper.rb#L48 + :param backtime: how many seconds substract each time (defaults to one day) :type backtime: int :param max_time: seconds since epoch (optional, diaspy'll figure everything on its own) :type max_time: int """ - if not max_time: max_time = self.max_time - backtime + + if not max_time: max_time = self.max_time self.max_time = max_time new_stream = self._obtain(max_time=max_time) self._expand(new_stream) @@ -225,17 +264,6 @@ class Outer(Generic): location = 'people/{}/stream.json'.format(guid) super().__init__(connection, location, fetch) - def _obtain(self, max_time=0): - """Obtains stream from pod. - """ - params = {} - if max_time: params['max_time'] = max_time - request = self._connection.get(self._location, params=params) - if request.status_code != 200: - raise errors.StreamError('wrong status code: {0}'.format(request.status_code)) - return [Post(self._connection, post['id']) for post in request.json()] - - class Stream(Generic): """The main stream containing the combined posts of the followed users and tags and the community spotlights posts @@ -243,21 +271,34 @@ class Stream(Generic): """ location = 'stream.json' - def post(self, text='', aspect_ids='public', photos=None, photo='', provider_display_name=''): + def post(self, text='', aspect_ids='public', photos=None, photo='', poll_question=None, poll_answers=None, location_coords=None, provider_display_name=''): """This function sends a post to an aspect. If both `photo` and `photos` are specified `photos` takes precedence. :param text: Text to post. :type text: str + :param aspect_ids: Aspect ids to send post to. :type aspect_ids: str + :param photo: filename of photo to post :type photo: str + :param photos: id of photo to post (obtained from _photoupload()) :type photos: int + :param provider_display_name: name of provider displayed under the post :type provider_display_name: str + :param poll_question: Question string + :type poll_question: str + + :param poll_answers: Anwsers to the poll + :type poll_answers: list with strings + + :param location_coords: TODO + :type location_coords: TODO + :returns: diaspy.models.Post -- the Post which has been created """ data = {} @@ -265,6 +306,10 @@ class Stream(Generic): data['status_message'] = {'text': text, 'provider_display_name': provider_display_name} if photo: data['photos'] = self._photoupload(photo) if photos: data['photos'] = photos + if poll_question and poll_answers: + data['poll_question'] = poll_question + data['poll_answers'] = poll_answers + if location_coords: data['location_coords'] = location_coords request = self._connection.post('status_messages', data=json.dumps(data), @@ -273,9 +318,8 @@ class Stream(Generic): 'x-csrf-token': repr(self._connection)}) if request.status_code != 201: raise Exception('{0}: Post could not be posted.'.format(request.status_code)) - data = request.json() - post = Post(self._connection, data['guid']) - post.data(data) + post_json = request.json() + post = Post(self._connection, id=post_json['id'], guid=post_json['guid'], post_data=post_json) return post def _photoupload(self, filename, aspects=[]): @@ -370,8 +414,8 @@ class Aspects(Generic): :parameter ids: list of apsect ids :type ids: list of integers """ - self._location = 'aspects.json' + '?{0}'.format(','.join(ids)) - self.fill() + self._location = 'aspects.json?a_ids[]=' + '{}'.format('&a_ids[]='.join(ids)) + self.fill() # this will create entirely new list of posts. def add(self, aspect_name, visible=0): """This function adds a new aspect. -- 2.25.1