interpals-api 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,243 @@
1
+ """
2
+ Session instance keeps the necessary cookie parameters so API calls work
3
+ correctly. Session can be created from raw cookie data or emulate the login
4
+ process using username and password.
5
+ """
6
+
7
+ import json
8
+ from urllib.parse import urljoin
9
+ from typing import Dict, TextIO, Self
10
+
11
+ import requests
12
+ import aiohttp
13
+
14
+ from .utils import find_csrf_token
15
+ from .errors import (NoCSRFTokenError, WrongUsernameOrPasswordError,
16
+ SessionError, TooManyLoginAttemptsError)
17
+ from .cookie import Cookie
18
+
19
+
20
+ class Session:
21
+ """
22
+ Synchronous session class.
23
+ """
24
+
25
+ def __init__(self, username: str, interpals_sessid: str,
26
+ csrf_cookieV2: str):
27
+ """
28
+ Create a new session instance from the necessary cookie data:
29
+ `username`, `interpals_sessid`, `csrf_cookieV2`.
30
+ """
31
+ self.username = username
32
+ """
33
+ `username`
34
+ """
35
+
36
+ self.interpals_sessid = interpals_sessid
37
+ """
38
+ `interpals_sessid` cookie
39
+ """
40
+
41
+ self.csrf_cookieV2 = csrf_cookieV2
42
+ """
43
+ `csrf_cookieV2` cookie
44
+ """
45
+
46
+ def __repr__(self) -> str:
47
+ return f"Session(username={self.username}, " \
48
+ f"interpals_sessid={self.interpals_sessid}, " \
49
+ f"csrf_cookieV2={self.csrf_cookieV2})"
50
+
51
+ def cookie(self) -> Dict[str, str]:
52
+ """
53
+ Get cookie data as a dictionary.
54
+ """
55
+ return {
56
+ 'interpals_sessid': self.interpals_sessid,
57
+ 'csrf_cookieV2': self.csrf_cookieV2
58
+ }
59
+
60
+ def dump(self, f: TextIO):
61
+ """
62
+ Dump session data into a file-like object.
63
+ """
64
+ kwargs = {
65
+ 'username': self.username,
66
+ 'interpals_sessid': self.interpals_sessid,
67
+ 'csrf_cookieV2': self.csrf_cookieV2,
68
+ }
69
+ json.dump(kwargs, f)
70
+
71
+ @classmethod
72
+ def load(cls, f: TextIO) -> Self:
73
+ """
74
+ Load session instance from a file-like object.
75
+ """
76
+ kwargs = json.load(f)
77
+ return cls(**kwargs)
78
+
79
+ @classmethod
80
+ def login(cls, username: str, password: str) -> Self:
81
+ """
82
+ Emulate the login process. It will return a session instance on success.
83
+ """
84
+ # Cookie object
85
+ cookie = Cookie()
86
+
87
+ # Request initial page
88
+ csrf_token = cls._request_initial_page(cookie)
89
+
90
+ # Request login endpoint
91
+ cls._request_login_endpoint(username, password, cookie, csrf_token)
92
+
93
+ # Create and return session instance
94
+ return cls(username=username,
95
+ interpals_sessid=cookie['interpals_sessid'],
96
+ csrf_cookieV2=cookie['csrf_cookieV2'])
97
+
98
+ @classmethod
99
+ def _request_initial_page(cls, cookie):
100
+ with requests.get("https://www.interpals.net/") as resp:
101
+ # Get initial cookie
102
+ set_cookie = Cookie.from_response_headers(resp.headers)
103
+ cookie.update(set_cookie)
104
+
105
+ # Extract CSRF token from the HTML
106
+ csrf_token = find_csrf_token(resp.text)
107
+
108
+ # Raise error if no token found
109
+ if csrf_token is None:
110
+ raise NoCSRFTokenError()
111
+
112
+ return csrf_token
113
+
114
+ @classmethod
115
+ def _request_login_endpoint(cls, username, password, cookie, csrf_token):
116
+ headers = {
117
+ 'Content-Type': 'application/x-www-form-urlencoded',
118
+ 'Cookie': cookie.as_string(),
119
+ 'Referer': 'https://www.interpals.net/',
120
+ }
121
+ data = {
122
+ 'username': username,
123
+ 'password': password,
124
+ 'csrf_token': csrf_token
125
+ }
126
+
127
+ # Request login entry point
128
+ with requests.post("https://www.interpals.net/app/auth/login",
129
+ data=data, headers=headers,
130
+ allow_redirects=False) as resp:
131
+ # Update cookie
132
+ set_cookie = Cookie.from_response_headers(resp.headers)
133
+ cookie.update(set_cookie)
134
+
135
+ # Check status
136
+ if resp.status_code == 200:
137
+ raise WrongUsernameOrPasswordError()
138
+ elif resp.status_code == 302:
139
+ location = resp.headers['Location']
140
+ else:
141
+ raise SessionError(
142
+ f"Unknown response status while login: {resp.status_code}"
143
+ )
144
+
145
+ # Request the location to check success
146
+ url = urljoin("https://www.interpals.net", location)
147
+ headers = {
148
+ 'Content-Type': 'application/x-www-form-urlencoded',
149
+ 'Cookie': cookie.as_string(),
150
+ }
151
+ with requests.get(url, headers=headers) as resp:
152
+ if "Too many unsuccessful login attempts." in resp.text:
153
+ raise TooManyLoginAttemptsError()
154
+
155
+
156
+ class SessionAsync(Session):
157
+ """
158
+ Synchronous session class. It is inherited from [Session](#Session).
159
+ """
160
+
161
+ @classmethod
162
+ async def login(cls, username: str, password: str) -> Self:
163
+ """
164
+ Emulate the login process. It will return a session instance on success.
165
+ """
166
+ # Cookie object
167
+ cookie = Cookie()
168
+
169
+ # Request initial page
170
+ csrf_token = await cls._request_initial_page(cookie)
171
+
172
+ # Request login endpoint
173
+ await cls._request_login_endpoint(
174
+ username, password, cookie, csrf_token
175
+ )
176
+
177
+ # Create and return session instance
178
+ return cls(username=username,
179
+ interpals_sessid=cookie['interpals_sessid'],
180
+ csrf_cookieV2=cookie['csrf_cookieV2'])
181
+
182
+ @classmethod
183
+ async def _request_initial_page(cls, cookie):
184
+ async with aiohttp.ClientSession() as session:
185
+ async with session.get("https://www.interpals.net/") as resp:
186
+ # Get initial cookie
187
+ set_cookie = Cookie.from_response_headers(resp.headers)
188
+ cookie.update(set_cookie)
189
+
190
+ # Extract CSRF token from the HTML
191
+ text = await resp.text()
192
+ csrf_token = find_csrf_token(text)
193
+
194
+ # Raise error if no token found
195
+ if csrf_token is None:
196
+ raise NoCSRFTokenError()
197
+
198
+ return csrf_token
199
+
200
+ @classmethod
201
+ async def _request_login_endpoint(cls, username, password, cookie,
202
+ csrf_token):
203
+ headers = {
204
+ 'Content-Type': 'application/x-www-form-urlencoded',
205
+ 'Cookie': cookie.as_string(),
206
+ 'Referer': 'https://www.interpals.net/',
207
+ }
208
+ data = {
209
+ 'username': username,
210
+ 'password': password,
211
+ 'csrf_token': csrf_token
212
+ }
213
+
214
+ # Request login entry point
215
+ async with aiohttp.ClientSession() as session:
216
+ async with session.post("https://www.interpals.net/app/auth/login",
217
+ data=data, headers=headers,
218
+ allow_redirects=False) as resp:
219
+ # Update cookie
220
+ set_cookie = Cookie.from_response_headers(resp.headers)
221
+ cookie.update(set_cookie)
222
+
223
+ # Check status
224
+ if resp.status == 200:
225
+ raise WrongUsernameOrPasswordError()
226
+ elif resp.status == 302:
227
+ location = resp.headers['Location']
228
+ else:
229
+ raise SessionError(
230
+ f"Unknown response status while login: {resp.status}"
231
+ )
232
+
233
+ # Request the location to check success
234
+ url = urljoin("https://www.interpals.net", location)
235
+ headers = {
236
+ 'Content-Type': 'application/x-www-form-urlencoded',
237
+ 'Cookie': cookie.as_string(),
238
+ }
239
+ async with aiohttp.ClientSession() as session:
240
+ async with session.get(url, headers=headers) as resp:
241
+ text = await resp.text()
242
+ if "Too many unsuccessful login attempts." in text:
243
+ raise TooManyLoginAttemptsError()
interpals_api/utils.py ADDED
@@ -0,0 +1,23 @@
1
+ """
2
+ Useful functions.
3
+ """
4
+
5
+ import re
6
+ from typing import Optional
7
+
8
+
9
+ re_csfr_token = re.compile(r'<meta name="csrf_token" content="(.*?)"')
10
+ """
11
+ Regex for CSRF token meta tag, it selects its value.
12
+ """
13
+
14
+
15
+ def find_csrf_token(html: str) -> Optional[str]:
16
+ """
17
+ Extract the content of CSRF token in the meta tag from HTML.
18
+ """
19
+ match = re_csfr_token.search(html)
20
+ if match is not None:
21
+ return match.group(1)
22
+ else:
23
+ return None
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: interpals-api
3
+ Version: 2.3.0
4
+ Summary: Unofficial Python API for Interpals.net.
5
+ Home-page: https://github.com/fomalhaut88/interpals-api
6
+ Author: Alexander Khlebushchev
7
+ License: MIT
8
+ Project-URL: Homepage, https://pypi.org/project/interpals-api/
9
+ Project-URL: Documentation, https://fomalhaut88.github.io/interpals-api/
10
+ Project-URL: Source, https://github.com/fomalhaut88/interpals-api
11
+ Project-URL: Issues, https://github.com/fomalhaut88/interpals-api/issues
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Internet :: WWW/HTTP
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Operating System :: OS Independent
23
+ Requires-Python: >=3.12
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: aiohttp>=3.12
26
+ Requires-Dist: requests>=2.32
27
+ Requires-Dist: lxml>=6.0
28
+ Requires-Dist: beautifulsoup4>=4.13
29
+ Dynamic: author
30
+ Dynamic: classifier
31
+ Dynamic: description
32
+ Dynamic: description-content-type
33
+ Dynamic: home-page
34
+ Dynamic: license
35
+ Dynamic: project-url
36
+ Dynamic: requires-dist
37
+ Dynamic: requires-python
38
+ Dynamic: summary
39
+
40
+ # interpals-api
41
+
42
+ This Python library provides a simple HTTP API for working with the website https://interpals.net/. It includes:
43
+
44
+ * retrieving data by user identifier
45
+ * listing visitors
46
+ * searching for users by criterions
47
+ * viewing and sending messages, deleting chats
48
+ * listing, adding and removing friends
49
+ * listing albums and pictures
50
+
51
+ ## Installation
52
+
53
+ ```
54
+ pip install interpals-api
55
+ ```
56
+
57
+ ## Basic example
58
+
59
+ ```python
60
+ from interpals_api import Session, Api
61
+
62
+ session = Session.login('yourusername', 'yourpassword')
63
+ api = Api(session)
64
+
65
+ user_info = api.profile('someuser')
66
+ print(user_info)
67
+ ```
68
+
69
+ Since version `2.1` it supports asynchronous calls for each api method. Below there is a brief asynchonous example:
70
+
71
+ ```python
72
+ from interpals_api import SessionAsync, ApiAsync
73
+
74
+ session = await SessionAsync.login('yourusername', 'yourpassword')
75
+ api = ApiAsync(session)
76
+
77
+ user_info = await api.profile('someuser')
78
+ print(user_info)
79
+ ```
80
+
81
+ ## Other examples
82
+
83
+ Basic example to retrieve user info:
84
+
85
+ ```python
86
+ from interpals_api import Session, Api
87
+
88
+ session = Session.login('yourusername', 'yourpassword')
89
+ api = Api(session)
90
+
91
+ # This gives info of the user with the link: https://www.interpals.net/someuser
92
+ user_info = api.profile('someuser')
93
+ ```
94
+
95
+ Dump and load session object:
96
+
97
+ ```python
98
+ from interpals_api import Session, Api
99
+
100
+ session = Session.login('yourusername', 'yourpassword')
101
+
102
+ # Save session into a file
103
+ with open('session.json', 'w') as f:
104
+ session.dump(f)
105
+
106
+ # Load session from the saved file
107
+ with open('session.json') as f:
108
+ session = Session.load(f)
109
+ ```
110
+
111
+ Viewing and searching for users:
112
+
113
+ ```python
114
+ from interpals_api import Session, Api
115
+
116
+ session = Session.login('yourusername', 'yourpassword')
117
+ api = Api(session)
118
+
119
+ # Viewing a certain user
120
+ api.view('someuser')
121
+
122
+ # Showing the visitors of your profile
123
+ users = api.visitors()
124
+ print(users)
125
+
126
+ # Searching for users
127
+ options = {
128
+ 'age1': '20',
129
+ 'age2': '40',
130
+ 'sex': ['male', 'female'],
131
+ 'continents': ['AF', 'AS', 'EU', 'NA', 'OC', 'SA'],
132
+ 'countries': ['US', 'CA', 'UK', 'RU'],
133
+ 'keywords': 'travelling',
134
+ 'online': True,
135
+ 'cityName': 'London' # Or you can use 'city' with its direct code on the website (it's faster)
136
+ }
137
+ for user in api.search(options, limit=100, timeout=1.0):
138
+ print(user)
139
+ ```
140
+
141
+ To work with friends and pictures, it is necessary to use `uid` in methods:
142
+
143
+ ```python
144
+ from interpals_api import Session, Api
145
+
146
+ session = Session.login('yourusername', 'yourpassword')
147
+ api = Api(session)
148
+
149
+ uid = api.get_uid('someuser')
150
+
151
+ # Listing friends of the user 'someuser'
152
+ friends = api.friends(uid)
153
+ print(friends)
154
+
155
+ # Adding 'someuser' to friends
156
+ api.friend_add(uid)
157
+
158
+ # Removing 'someuser' from friends
159
+ api.friend_remove(uid)
160
+
161
+ # Listing pictures
162
+ albums = api.albums(uid)
163
+ for album in albums:
164
+ aid = album['aid']
165
+ pictures = api.pictures(uid, aid)
166
+ for picture in pictures:
167
+ print(picture['src'])
168
+ ```
169
+
170
+ Working with the chat is done through `thread_id`:
171
+
172
+ ```python
173
+ from interpals_api import Session, Api
174
+
175
+ session = Session.login('yourusername', 'yourpassword')
176
+ api = Api(session)
177
+
178
+ uid = api.get_uid('someuser')
179
+
180
+ # Listing your chats
181
+ result = api.chat(count=9, offset=0)
182
+ print(result['chats'])
183
+
184
+ # Staring new chat (getting thread_id)
185
+ thread_id = api.get_thread_id(uid)
186
+
187
+ # Show chat messages
188
+ messages = api.chat_messages(thread_id)
189
+ print(messages)
190
+
191
+ # Send new message to the thread
192
+ api.chat_send(thread_id, "Hey! How are you?")
193
+
194
+ # Removing chat
195
+ api.chat_delete(thread_id)
196
+ ```
@@ -0,0 +1,15 @@
1
+ interpals_api/__init__.py,sha256=1kK2H0Y0z-ogmm5_kWV40PZjM03yaDi2SBfTd21zo64,3526
2
+ interpals_api/api.py,sha256=KQ8RhMV1sQR_R7xlM5jr0ymgSUMItzRXfi9ZkCMHNRc,20286
3
+ interpals_api/cookie.py,sha256=qmgZNzBYPEkaBlRvBwEA0LYNoWiyQGEYLfzYF4VmcCU,1410
4
+ interpals_api/errors.py,sha256=8F8RwegyWM1ZY7w428PDxcCnyfkCSCUFHBKRNv20DVk,923
5
+ interpals_api/session.py,sha256=Lp1yStrhMZaWYnMTH-gGIxnQZKyFKGeqUjOtIwzhQrw,8035
6
+ interpals_api/utils.py,sha256=0pHsn4prOE_OuiEBJ8xyd18z35UBOYdULn2Cho6guos,456
7
+ interpals_api/parsers/__init__.py,sha256=fUWisdZwAqbL9-YCL0fL1Z-Syhh8fsFSnkktPYpRJC0,46
8
+ interpals_api/parsers/chat_parser.py,sha256=8tnef9ZfhyDqSB3YSwhuQuqA5nCAAjET75zLV7ACIYA,4298
9
+ interpals_api/parsers/friends_parser.py,sha256=Sb_ZDma0ynF6BVdSvHT_YSRt8QsgzU9Z-MWW9osIP5E,1326
10
+ interpals_api/parsers/pictures_parser.py,sha256=OhG_pkVJjzAgMHeIT_UF_bFjI4hRyG0EU9EZJqpxURw,2716
11
+ interpals_api/parsers/profile_parser.py,sha256=ZrTgHPRmsx7Ws0VIn7jpBSwVZ-EDFfrK41g5YYd6uf0,3863
12
+ interpals_api-2.3.0.dist-info/METADATA,sha256=IqTA0Uq9_fS_2OxVHzJlhL5-PET-7YKPcemo9w-O9TM,4902
13
+ interpals_api-2.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ interpals_api-2.3.0.dist-info/top_level.txt,sha256=dXTmgNrsv3Y2J5bKnb3eWyjR8KnF4zRZqkomSAMP-Mo,14
15
+ interpals_api-2.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ interpals_api