Rename getToken() to get_token()
[diaspy.git] / diaspy / streams.py
CommitLineData
1232dac5
MM
1import json
2from diaspy.models import Post
3
505fc964
MM
4"""Docstrings for this module are taken from:
5https://gist.github.com/MrZYX/01c93096c30dc44caf71
6
7Documentation for D* JSON API taken from:
8http://pad.spored.de/ro/r.qWmvhSZg7rk4OQam
9"""
1232dac5
MM
10
11class Generic:
505fc964 12 """Object representing generic stream. Used in Tag(),
1232dac5
MM
13 Stream(), Activity() etc.
14 """
505fc964 15 def __init__(self, connection, location=''):
1232dac5
MM
16 """
17 :param connection: Connection() object
505fc964
MM
18 :type connection: diaspy.connection.Connection
19 :param location: location of json
20 :type location: str
1232dac5
MM
21 """
22 self._connection = connection
505fc964
MM
23 self._setlocation()
24 if location: self._location = location
1232dac5
MM
25 self._stream = []
26 self.fill()
27
28 def __contains__(self, post):
29 """Returns True if stream contains given post.
30 """
31 if type(post) is not Post:
32 raise TypeError('stream can contain only posts: checked for {0}'.format(type(post)))
33 return post in self._stream
34
35 def __iter__(self):
36 """Provides iterable interface for stream.
37 """
38 return iter(self._stream)
39
40 def __getitem__(self, n):
41 """Returns n-th item in Stream.
42 """
43 return self._stream[n]
44
45 def __len__(self):
46 """Returns length of the Stream.
47 """
48 return len(self._stream)
49
505fc964
MM
50 def _setlocation(self):
51 """Sets location of the stream.
52 Location defaults to 'stream.json'
53
54 NOTICE: inheriting objects should override this method
55 and set their own value to the `location`.
56 However, it is possible to override default location by
57 passing the desired one to the constructor.
58 For example:
59
60 def _setlocation(self):
61 self._location = 'foo.json'
62
63
64 :param location: url of the stream
65 :type location: str
66
67 :returns: str
68 """
69 self._location = 'stream.json'
70
1232dac5
MM
71 def _obtain(self):
72 """Obtains stream from pod.
73 """
74 request = self._connection.get(self._location)
75 if request.status_code != 200:
76 raise Exception('wrong status code: {0}'.format(request.status_code))
77 return [Post(str(post['id']), self._connection) for post in request.json()]
78
79 def clear(self):
80 """Removes all posts from stream.
81 """
82 self._stream = []
83
505fc964
MM
84 def purge(self):
85 """Removes all unexistent posts from stream.
86 """
87 stream = []
88 for post in self._stream:
89 deleted = False
90 try:
91 post.get_data()
92 stream.append(post)
93 except Exception:
94 deleted = True
95 finally:
96 if not deleted: stream.append(post)
97 self._stream = stream
98
1232dac5
MM
99 def update(self):
100 """Updates stream.
101 """
505fc964
MM
102 new_stream = self._obtain()
103 ids = [post.post_id for post in self._stream]
104
105 stream = self._stream
106 for i in range(len(new_stream)):
107 if new_stream[-i].post_id not in ids:
108 stream = [new_stream[-i]] + stream
109 ids.append(new_stream[-i].post_id)
110
111 self._stream = stream
1232dac5
MM
112
113 def fill(self):
114 """Fills the stream with posts.
115 """
116 self._stream = self._obtain()
117
118
119class Stream(Generic):
505fc964
MM
120 """The main stream containing the combined posts of the
121 followed users and tags and the community spotlights posts
122 if the user enabled those.
1232dac5 123 """
505fc964
MM
124 def _setlocation(self):
125 self._location = 'stream.json'
126
1232dac5
MM
127 def post(self, text, aspect_ids='public', photos=None):
128 """This function sends a post to an aspect
129
130 :param text: Text to post.
131 :type text: str
132 :param aspect_ids: Aspect ids to send post to.
133 :type aspect_ids: str
134
135 :returns: diaspy.models.Post -- the Post which has been created
136 """
137 data = {}
138 data['aspect_ids'] = aspect_ids
139 data['status_message'] = {'text': text}
140 if photos: data['photos'] = photos
141 request = self._connection.post('status_messages',
142 data=json.dumps(data),
143 headers={'content-type': 'application/json',
144 'accept': 'application/json',
59ad210c 145 'x-csrf-token': self._connection.get_token()})
1232dac5
MM
146 if request.status_code != 201:
147 raise Exception('{0}: Post could not be posted.'.format(
148 request.status_code))
149
150 post = Post(str(request.json()['id']), self._connection)
151 return post
152
153 def post_picture(self, filename):
154 """This method posts a picture to D*.
155
156 :param filename: Path to picture file.
157 :type filename: str
158 """
159 aspects = self._connection.getUserInfo()['aspects']
160 params = {}
161 params['photo[pending]'] = 'true'
162 params['set_profile_image'] = ''
163 params['qqfile'] = filename
164 for i, aspect in enumerate(aspects):
165 params['photo[aspect_ids][%d]' % (i)] = aspect['id']
166
167 data = open(filename, 'rb')
168
169 headers = {'content-type': 'application/octet-stream',
59ad210c 170 'x-csrf-token': self._connection.get_token(),
1232dac5
MM
171 'x-file-name': filename}
172 request = self._connection.post('photos', params=params, data=data, headers=headers)
173 data.close()
174 return request
175
176
177class Activity(Generic):
178 """Stream representing user's activity.
179 """
505fc964
MM
180 def _setlocation(self):
181 self._location = 'activity.json'
182
183 def _delid(self, id):
184 """Deletes post with given id.
185 """
186 post = None
187 for p in self._stream:
188 if p['id'] == id:
189 post = p
190 break
191 if post is not None: post.delete()
192
193 def delete(self, post):
194 """Deletes post from users activity.
195 `post` can be either post id or Post()
196 object which will be identified and deleted.
197 After deleting post the stream will be filled.
198
199 :param post: post identifier
200 :type post: str, diaspy.models.Post
201 """
202 if type(post) == str: self._delid(post)
203 elif type(post) == Post: post.delete()
204 else:
205 raise TypeError('this method accepts only int, str or Post: {0} given')
206 self.fill()
207
208
209class Aspects(Generic):
210 """This stream contains the posts filtered by
211 the specified aspect IDs. You can choose the aspect IDs with
212 the parameter `aspect_ids` which value should be
213 a comma seperated list of aspect IDs.
214 If the parameter is ommitted all aspects are assumed.
215 An example call would be `aspects.json?aspect_ids=23,5,42`
216 """
217 def _setlocation(self):
218 self._location = 'aspects.json'
219
220 def add(self, aspect_name, visible=0):
221 """This function adds a new aspect.
222 """
59ad210c 223 data = {'authenticity_token': self._connection.get_token(),
505fc964
MM
224 'aspect[name]': aspect_name,
225 'aspect[contacts_visible]': visible}
226
227 r = self._connection.post('aspects', data=data)
228 if r.status_code != 200:
229 raise Exception('wrong status code: {0}'.format(r.status_code))
230
231
232class Commented(Generic):
233 """This stream contains all posts
234 the user has made a comment on.
235 """
236 def _setlocation(self):
237 self._location = 'commented.json'
238
239
240class Liked(Generic):
241 """This stream contains all posts the user liked.
242 """
243 def _setlocation(self):
244 self._location = 'liked.json'
245
246
247class Mentions(Generic):
248 """This stream contains all posts
249 the user is mentioned in.
250 """
251 def _setlocation(self):
252 self._location = 'mentions.json'
253
254
255class FollowedTags(Generic):
256 """This stream contains all posts
257 containing a tag the user is following.
258 """
259 def _setlocation(self):
260 self._location = 'followed_tags.json'
261
262 def add(self, tag_name):
263 """Follow new tag.
505fc964
MM
264
265 :param tag_name: tag name
266 :type tag_name: str
267 """
f0fa9fec 268 data = {'name':tag_name,
59ad210c 269 'authenticity_token':self._connection.get_token(),
505fc964 270 }
505fc964 271
f0fa9fec 272 headers={'content-type': 'application/json',
59ad210c 273 'x-csrf-token': self._connection.get_token(),
f0fa9fec
MK
274 'accept': 'application/json'}
275
276 request = self._connection.post('tag_followings', data=json.dumps(data), headers=headers)
277
278 if request.status_code != 201:
505fc964 279 raise Exception('wrong error code: {0}'.format(request.status_code))