You can fetch tag suggestions using diaspy
[diaspy.git] / diaspy / connection.py
CommitLineData
a29d3526
MM
1#!/usr/bin/env python
2
8993810c
MM
3import re
4import requests
7a31b4aa 5import json
ff3d2ab4 6import warnings
8993810c 7
2cf8467c
MM
8from diaspy import errors
9
a29d3526 10
178faa46
MM
11"""This module abstracts connection to pod.
12"""
13
14
a29d3526 15class Connection():
1cff2093 16 """Object representing connection with the pod.
a29d3526 17 """
27a28aaf
MM
18 _token_regex = re.compile(r'content="(.*?)"\s+name="csrf-token')
19 _userinfo_regex = re.compile(r'window.current_user_attributes = ({.*})')
5de52803 20 _userinfo_regex_2 = re.compile(r'gon.user=({.*});gon.preloads')
27a28aaf 21
615edb73 22 def __init__(self, pod, username='', password='', schema='https'):
8993810c
MM
23 """
24 :param pod: The complete url of the diaspora pod to use.
25 :type pod: str
26 :param username: The username used to log in.
27 :type username: str
28 :param password: The password used to log in.
29 :type password: str
30 """
a29d3526 31 self.pod = pod
2cf8467c
MM
32 self._session = requests.Session()
33 self._login_data = {}
5de52803 34 self._userdata = {}
2cf8467c 35 self._token = ''
5de52803 36 self._diaspora_session = ''
2cf8467c
MM
37 try:
38 self._setlogin(username, password)
75a456f4
MM
39 except requests.exceptions.MissingSchema:
40 self.pod = '{0}://{1}'.format(schema, self.pod)
41 warnings.warn('schema was missing')
2cf8467c
MM
42 finally:
43 pass
44 try:
45 self._setlogin(username, password)
46 except Exception as e:
47 raise errors.LoginError('cannot create login data (caused by: {0})'.format(e))
8993810c 48
d4bd92cd
MM
49 def __repr__(self):
50 """Returns token string.
51 It will be easier to change backend if programs will just use:
52 repr(connection)
53 instead of calling a specified method.
54 """
cf6a800f 55 return self._fetchtoken()
d4bd92cd 56
73a9e0d3 57 def get(self, string, headers={}, params={}, direct=False):
8993810c
MM
58 """This method gets data from session.
59 Performs additional checks if needed.
60
61 Example:
7a31b4aa 62 To obtain 'foo' from pod one should call `get('foo')`.
8993810c
MM
63
64 :param string: URL to get without the pod's URL and slash eg. 'stream'.
65 :type string: str
73a9e0d3
MM
66 :param direct: if passed as True it will not be expanded
67 :type direct: bool
8993810c 68 """
73a9e0d3
MM
69 if not direct: url = '{0}/{1}'.format(self.pod, string)
70 else: url = string
71 return self._session.get(url, params=params, headers=headers)
8993810c
MM
72
73 def post(self, string, data, headers={}, params={}):
74 """This method posts data to session.
75 Performs additional checks if needed.
76
77 Example:
7a31b4aa 78 To post to 'foo' one should call `post('foo', data={})`.
8993810c
MM
79
80 :param string: URL to post without the pod's URL and slash eg. 'status_messages'.
81 :type string: str
82 :param data: Data to post.
83 :param headers: Headers (optional).
84 :type headers: dict
85 :param params: Parameters (optional).
86 :type params: dict
87 """
88 string = '{0}/{1}'.format(self.pod, string)
2cf8467c 89 request = self._session.post(string, data, headers=headers, params=params)
8993810c
MM
90 return request
91
178faa46
MM
92 def put(self, string, data=None, headers={}, params={}):
93 """This method PUTs to session.
94 """
95 string = '{0}/{1}'.format(self.pod, string)
2cf8467c
MM
96 if data is not None: request = self._session.put(string, data, headers=headers, params=params)
97 else: request = self._session.put(string, headers=headers, params=params)
178faa46
MM
98 return request
99
8993810c
MM
100 def delete(self, string, data, headers={}):
101 """This method lets you send delete request to session.
102 Performs additional checks if needed.
103
104 :param string: URL to use.
105 :type string: str
106 :param data: Data to use.
107 :param headers: Headers to use (optional).
108 :type headers: dict
109 """
110 string = '{0}/{1}'.format(self.pod, string)
2cf8467c 111 request = self._session.delete(string, data=data, headers=headers)
8993810c
MM
112 return request
113
114 def _setlogin(self, username, password):
115 """This function is used to set data for login.
b0b4c46d 116
8993810c
MM
117 .. note::
118 It should be called before _login() function.
119 """
2cf8467c
MM
120 self._login_data = {'user[username]': username,
121 'user[password]': password,
122 'authenticity_token': self._fetchtoken()}
8993810c 123
385e7ebe
MM
124 def _login(self):
125 """Handles actual login request.
126 Raises LoginError if login failed.
127 """
128 request = self.post('users/sign_in',
73a9e0d3 129 data=self._login_data)
5de52803 130 if request.status_code not in [200, 201]:
2cf8467c 131 raise errors.LoginError('{0}: login failed'.format(request.status_code))
73a9e0d3 132 self._diaspora_session = request.cookies['_diaspora_session']
385e7ebe
MM
133
134 def login(self, username='', password=''):
135 """This function is used to log in to a pod.
136 Will raise LoginError if password or username was not specified.
8993810c 137 """
385e7ebe 138 if username and password: self._setlogin(username, password)
2cf8467c
MM
139 if not self._login_data['user[username]'] or not self._login_data['user[password]']:
140 raise errors.LoginError('username and/or password is not specified')
385e7ebe 141 self._login()
2cf8467c 142 self._login_data = {}
385e7ebe 143
63cc182d
MM
144 def logout(self):
145 """Logs out from a pod.
146 When logged out you can't do anything.
147 """
148 self.get('users/sign_out')
b0b4c46d 149 self.token = ''
63cc182d 150
2cf8467c 151 def podswitch(self, pod, username, password):
7a31b4aa 152 """Switches pod from current to another one.
385e7ebe
MM
153 """
154 self.pod = pod
2cf8467c 155 self._setlogin(username, password)
385e7ebe 156 self._login()
8993810c 157
5de52803 158 def getUserInfo(self, fetch=False):
7a31b4aa
MM
159 """This function returns the current user's attributes.
160
161 :returns: dict -- json formatted user info.
162 """
163 request = self.get('bookmarklet')
73a9e0d3
MM
164 userdata = self._userinfo_regex.search(request.text)
165 if userdata is None: userdata = self._userinfo_regex_2.search(request.text)
166 if userdata is None: raise errors.DiaspyError('cannot find user data')
167 userdata = userdata.group(1)
168 warnings.warn(userdata)
169 return json.loads(userdata)
7a31b4aa 170
b0b4c46d
MM
171 def _fetchtoken(self):
172 """This method tries to get token string needed for authentication on D*.
173
174 :returns: token string
175 """
176 request = self.get('stream')
177 token = self._token_regex.search(request.text).group(1)
2cf8467c 178 self._token = token
b0b4c46d 179 return token
2ec93347 180
2b6b487e 181 def get_token(self, fetch=True):
385e7ebe 182 """This function returns a token needed for authentication in most cases.
2cf8467c 183 **Notice:** using repr() is recommended method for getting token.
a29d3526 184
b0b4c46d
MM
185 Each time it is run a _fetchtoken() is called and refreshed token is stored.
186
187 It is more safe to use than _fetchtoken().
ff3d2ab4 188 By setting new you can request new token or decide to get stored one.
2b6b487e 189 If no token is stored new one will be fetched anyway.
a29d3526 190
8993810c
MM
191 :returns: string -- token used to authenticate
192 """
b0b4c46d 193 try:
6d8d47ce 194 if fetch: self._fetchtoken()
2cf8467c 195 if not self._token: self._fetchtoken()
b0b4c46d 196 except requests.exceptions.ConnectionError as e:
ff3d2ab4 197 warnings.warn('{0} was cought: reusing old token'.format(e))
b0b4c46d 198 finally:
2cf8467c
MM
199 if not self._token: raise errors.TokenError('cannot obtain token and no previous token found for reuse')
200 return self._token
5de52803
MM
201
202 def getSessionToken(self):
203 """Returns session token string (_diaspora_session).
204 """
205 return self._diaspora_session