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,65 @@
1
+ """
2
+ The project error tree.
3
+ """
4
+
5
+ class InterpalsAPIError(Exception):
6
+ """
7
+ Base InterpalsAPI error.
8
+ """
9
+ pass
10
+
11
+
12
+ class SessionError(InterpalsAPIError):
13
+ """
14
+ General session error.
15
+ """
16
+ pass
17
+
18
+
19
+ class APIError(InterpalsAPIError):
20
+ """
21
+ General API error.
22
+ """
23
+ pass
24
+
25
+
26
+ class NoCSRFTokenError(SessionError):
27
+ """
28
+ CSRF token not found on session create.
29
+ """
30
+ pass
31
+
32
+
33
+ class WrongUsernameOrPasswordError(SessionError):
34
+ """
35
+ Invalid credentials on session create.
36
+ """
37
+ pass
38
+
39
+
40
+ class TooManyLoginAttemptsError(SessionError):
41
+ """
42
+ Too many attempts on session create.
43
+ """
44
+ pass
45
+
46
+
47
+ class APITimeoutError(APIError):
48
+ """
49
+ Timeout error in API call.
50
+ """
51
+ pass
52
+
53
+
54
+ class APIAuthError(APIError):
55
+ """
56
+ Authorization error in API call.
57
+ """
58
+ pass
59
+
60
+
61
+ class APIRedirectError(APIError):
62
+ """
63
+ Redirect error in API call.
64
+ """
65
+ pass
@@ -0,0 +1,3 @@
1
+ """
2
+ Different HTML parser of the website.
3
+ """
@@ -0,0 +1,132 @@
1
+ """
2
+ Parser for the chat. It supports flexible chats browsing, general information
3
+ and fetching the messages from the history.
4
+ """
5
+
6
+ from typing import List, Dict, Any
7
+
8
+ from bs4 import BeautifulSoup
9
+
10
+
11
+ class ChatParser:
12
+ """
13
+ Chat parser:
14
+
15
+ ```python
16
+ parser = ChatParser()
17
+ maxmsgid = parser.parse_maxmsgid(html)
18
+ unread = parser.parse_unread(html)
19
+ chats = parser.parse_chat(html)
20
+ messages = parser.parse_messages(html)
21
+ ```
22
+ """
23
+
24
+ def parse_maxmsgid(self, content: str) -> int:
25
+ """
26
+ Parse maximum id of existing chats from HTML content. It is needed to
27
+ request chats up to this number (as `max_msg_id` parameter).
28
+ """
29
+ soup = BeautifulSoup(content, "lxml")
30
+ maxmsgid = soup.find('div', id='threads_left')['data-max-msg-id']
31
+ return int(maxmsgid)
32
+
33
+ def parse_unread(self, content: str) -> int:
34
+ """
35
+ Parse the count of unread chats from HTML content.
36
+ """
37
+ soup = BeautifulSoup(content, "lxml")
38
+ unread = soup.find('span', id='pmNewCnt').text.strip()
39
+ return int(unread[2:-1]) if unread else 0
40
+
41
+ def parse_chat(self, content: str) -> List[Dict[str, Any]]:
42
+ """
43
+ Parse all chats from HTML content. They include thread_id (it's needed
44
+ to request chat messages) and other basic fields. The result is a list
45
+ of dictionaries.
46
+ """
47
+ soup = BeautifulSoup(content, "lxml")
48
+
49
+ chats = []
50
+ elements = soup.find_all('div', class_='pm_thread')
51
+ for element in elements:
52
+ chat = {}
53
+ chat.update(self._parse_thread_info(element))
54
+ chat.update(self._parse_user_info(element))
55
+ chat.update(self._parse_message_info(element))
56
+ chats.append(chat)
57
+
58
+ return chats
59
+
60
+ def parse_messages(self, content: str) -> List[Dict[str, Any]]:
61
+ """
62
+ Parse char messages from HTML content.
63
+ """
64
+ soup = BeautifulSoup(content, "lxml")
65
+
66
+ messages = []
67
+
68
+ elements = soup.find_all('div', class_=['pm_date', 'pm_msg'])
69
+ user = None
70
+ date = None
71
+ for element in elements:
72
+ if 'pm_date' in element['class']:
73
+ date = element.text
74
+
75
+ else:
76
+ msg_user_thumb = element.find('div', class_='msg_user_thumb')
77
+ if msg_user_thumb:
78
+ user = msg_user_thumb.a['href']
79
+
80
+ message = {}
81
+ message.update(self._parse_message(element))
82
+ message['date'] = date
83
+ message['user'] = user
84
+ messages.append(message)
85
+
86
+ return messages
87
+
88
+ def _parse_thread_info(self, element):
89
+ data = {}
90
+ data['new'] = 'new' in element['class']
91
+ data['thread_id'] = element['id'][7:]
92
+ return data
93
+
94
+ def _parse_user_info(self, element):
95
+ data = {}
96
+
97
+ tui_el = element.find_all('div', class_='tui_el')
98
+ user_age = tui_el[0].text.strip().split(', ')
99
+ if len(user_age) == 2:
100
+ data['user'], data['age'] = user_age
101
+ else:
102
+ data['user'], data['age'] = user_age[0], None
103
+ data['city'] = tui_el[1].text if len(tui_el) > 1 else None
104
+
105
+ tui_el_0_class = tui_el[0]['class']
106
+ data['sex'] = tui_el_0_class[1] if len(tui_el_0_class) > 1 else None
107
+
108
+ data['avatar'] = 'https:' + element.find(class_='thumb')['src']
109
+
110
+ data['flag'] = None
111
+ flag_img = element.find('div', class_='tui_flag').img
112
+ if flag_img:
113
+ data['flag'] = 'https:' + flag_img['src']
114
+
115
+ data['online'] = bool(element.find(class_='online-now'))
116
+
117
+ return data
118
+
119
+ def _parse_message_info(self, element):
120
+ data = {}
121
+ data['message'] = element.find(class_='th_snippet').text.strip()
122
+ data['i_sent'] = bool(element.find(class_='snippet_thumb'))
123
+ data['unread'] = 'pm_new' in element.find(class_='th_snippet')['class']
124
+ return data
125
+
126
+ def _parse_message(self, element):
127
+ data = {}
128
+ data['time'] = element.find('div', class_='pm_time').text
129
+ data['text'] = element.find('div', class_='msg_body').text.strip()
130
+ data['msg_id'] = element['id'][4:]
131
+ data['unread'] = 'pm_unread' in element['class']
132
+ return data
@@ -0,0 +1,53 @@
1
+ """
2
+ Parser for user friends.
3
+ """
4
+
5
+ from typing import List, Dict, Any
6
+
7
+ from bs4 import BeautifulSoup
8
+
9
+
10
+ class FriendsParser:
11
+ """
12
+ Friends parser:
13
+
14
+ ```python
15
+ parser = FriendsParser()
16
+ data = parser.parse(html)
17
+ ```
18
+ """
19
+
20
+ def parse(self, content: str) -> List[Dict[str, Any]]:
21
+ """
22
+ Parse HTML content and return list of friends as dictionaries having
23
+ username, age, city, online status and avatar URL.
24
+ """
25
+ soup = BeautifulSoup(content, "lxml")
26
+
27
+ boxes = soup.find_all('div', class_='friendBox')
28
+ friends = []
29
+ for box in boxes:
30
+ friend = {}
31
+ friend.update(self._parse_user_info(box))
32
+ friend.update(self._parse_avatar(box))
33
+ friend.update(self._parse_online(box))
34
+ friends.append(friend)
35
+
36
+ return friends
37
+
38
+ def _parse_avatar(self, box):
39
+ avatar = 'https:' + box.img['src']
40
+ return {'avatar': avatar}
41
+
42
+ def _parse_user_info(self, box):
43
+ src = box.find('img', class_='status')['src']
44
+ online = 'online' in src
45
+ return {'online': online}
46
+
47
+ def _parse_online(self, box):
48
+ username, age, city, *extra = box.text.strip().split()
49
+ return {
50
+ 'user': username,
51
+ 'age': age,
52
+ 'city': city
53
+ }
@@ -0,0 +1,91 @@
1
+ """
2
+ Parser for user pictures. It supports the album structure.
3
+ """
4
+
5
+ from typing import List, Dict, Any
6
+
7
+ from bs4 import BeautifulSoup
8
+
9
+
10
+ class PicturesParser:
11
+ """
12
+ Pictures parser:
13
+
14
+ ```python
15
+ parser = PicturesParser()
16
+ albums_data = parser.parse_albums(albums_html)
17
+ ```
18
+
19
+ ```python
20
+ parser = PicturesParser()
21
+ pictures_data = parser.parse_pictures(pictures_html)
22
+ ```
23
+ """
24
+
25
+ def parse_albums(self, content: str) -> List[Dict[str, Any]]:
26
+ """
27
+ Parse HTML content and return album information incliding its `aid`,
28
+ description and pictures' URLs.
29
+ """
30
+ soup = BeautifulSoup(content, "lxml")
31
+
32
+ elements = soup.find_all('div', class_='editAlbumBox')
33
+ albums = []
34
+ for element in elements:
35
+ album = {}
36
+ album.update(self._parse_aid(element))
37
+ album.update(self._parse_info(element))
38
+ album.update(self._parse_album_pictures(element))
39
+ albums.append(album)
40
+
41
+ return albums
42
+
43
+ def parse_pictures(self, content: str) -> List[Dict[str, Any]]:
44
+ """
45
+ Parse HTML content and return a list of pictures as URLs of small and
46
+ big ones.
47
+ """
48
+ soup = BeautifulSoup(content, "lxml")
49
+
50
+ elements = soup.find_all('div', class_='albThumb')
51
+ pictures = []
52
+ for element in elements:
53
+ picture = {}
54
+ picture['src180x180'] = element.find('img')['src']
55
+ picture['src'] = ("https://ipstatic.net/photos/"
56
+ + picture['src180x180'].split('/180x180/')[1])
57
+ # picture['src'] = self._patch_src(picture['src'])
58
+ pictures.append(picture)
59
+
60
+ return pictures
61
+
62
+ def _parse_aid(self, element):
63
+ href = element.find('a', class_='albEditThumb')['href']
64
+ aid = href.split('&')[0].split('=')[-1]
65
+ return {'aid': aid}
66
+
67
+ def _parse_info(self, element):
68
+ name = element.find('h3').text
69
+ stat = element.find('div', class_='albumStats').text.split('|')
70
+ pics_number = int(stat[0].strip().split()[0])
71
+ created = stat[1].strip().split(None, 1)[1]
72
+ updated = stat[2].strip().split(None, 1)[1]
73
+ return {
74
+ 'name': name,
75
+ 'pics_number': pics_number,
76
+ 'created': created,
77
+ 'updated': updated
78
+ }
79
+
80
+ def _parse_album_pictures(self, element):
81
+ links = element.find_all('a', class_='thumb')
82
+ pics = [
83
+ link.find('img')['src']
84
+ for link in links
85
+ ]
86
+ return {'pictures': pics}
87
+
88
+ # def _patch_src(self, src):
89
+ # if src[-6] == '_':
90
+ # src = src[:-6] + src[-4:]
91
+ # return src
@@ -0,0 +1,124 @@
1
+ """
2
+ Parser for general profile data of a user. It includes gender, location,
3
+ online, join date, status message, avatar URL, uid number and the description.
4
+ """
5
+
6
+ from typing import Dict, Any
7
+
8
+ from bs4 import BeautifulSoup
9
+
10
+
11
+ class ProfileParser:
12
+ """
13
+ Parser class.
14
+
15
+ ```python
16
+ parser = ProfileParser()
17
+ data = parser.parse(html)
18
+ ```
19
+ """
20
+
21
+ def parse(self, content: str) -> Dict[str, Any]:
22
+ """
23
+ Parse HTML content and return the data as a dictionary.
24
+ """
25
+ soup = BeautifulSoup(content, "lxml")
26
+
27
+ data = {}
28
+
29
+ data.update(self._extract_name_age_sex(soup))
30
+ data.update(self._extract_location(soup))
31
+ data.update(self._extract_online(soup))
32
+ data.update(self._extract_joined_updated(soup))
33
+ data.update(self._extract_status(soup))
34
+ data.update(self._extract_avatar(soup))
35
+ data.update(self._extract_uid(soup))
36
+ data.update(self._extract_info(soup))
37
+
38
+ return data
39
+
40
+ def _extract_name_age_sex(self, soup):
41
+ profile_box = soup.find(class_='profileBox')
42
+
43
+ sex = profile_box.find('img')['src'].split('/')[-1][:-7]
44
+
45
+ pieces = profile_box.text.split('\n')
46
+ pieces = list(map(str.strip, pieces))
47
+ pieces = list(filter(lambda s: s, pieces))
48
+ piece = pieces[1]
49
+ piece = piece.replace('y.o.', '')
50
+ na = piece.split(', ')
51
+ if len(na) == 2:
52
+ name, age = na
53
+ else:
54
+ if na.isdigit():
55
+ name, age = None, na
56
+ else:
57
+ name, age = na, None
58
+ age = int(age)
59
+
60
+ return {'name': name, 'age': age, 'sex': sex}
61
+
62
+ def _extract_location(self, soup):
63
+ links = soup.find(class_='profLocation').find_all('a')
64
+ city = links[1].text
65
+ city_code = links[1]['href'].split('=')[-1]
66
+ country = links[2].text
67
+ country_code = links[2]['href'][-2:]
68
+ return {
69
+ 'city': city,
70
+ 'city_code': city_code,
71
+ 'country': country,
72
+ 'country_code': country_code
73
+ }
74
+
75
+ def _extract_online(self, soup):
76
+ text = soup.find(class_='profOnlineStatus').text.strip()
77
+ if text.lower() == "online now":
78
+ online = True
79
+ last = None
80
+ else:
81
+ online = False
82
+ last = ' '.join(text.split()[1:])
83
+ return {'online': online, 'last': last}
84
+
85
+ def _extract_joined_updated(self, soup):
86
+ text = soup.find(class_='profileBox').find_all('p')[0].text.strip()
87
+ joined, updated = text.split('\n')[1::2]
88
+ joined = joined[:-1]
89
+ updated = updated[:-1]
90
+ return {'joined': joined, 'updated': updated}
91
+
92
+ def _extract_status(self, soup):
93
+ element = soup.find('span', id='prStatMsgTxt')
94
+ status = element.text if element else ''
95
+ return {'status': status}
96
+
97
+ def _extract_avatar(self, soup):
98
+ avatar_link = soup.find('a', class_='mainPhoto')
99
+ if not avatar_link:
100
+ avatar_link = soup.find('a', class_='mpImgLink')
101
+ avatar = 'https:' + avatar_link.find('img')['src']
102
+ return {'avatar': avatar}
103
+
104
+ def _extract_uid(self, soup):
105
+ report_link = soup.find('a', class_='profReportLink')
106
+ if report_link:
107
+ uid = report_link['user-id']
108
+ else:
109
+ uid = soup.find('div', class_='hidden-uid').text
110
+ return {'uid': uid}
111
+
112
+ def _extract_info(self, soup):
113
+ box = soup.find(class_='profDataBox')
114
+ titles = box.find_all('h2')
115
+ texts = box.find_all('div', class_='profDataBoxText')
116
+ info = []
117
+ for title, text in zip(titles, texts):
118
+ title = title.text.strip()
119
+ text = text.text.strip()
120
+ info.append({
121
+ 'title': title,
122
+ 'text': text
123
+ })
124
+ return {'info': info}