]> git.lizzy.rs Git - nhentai.git/blob - nhentai/parser.py
a753b33e1f9cd63026f58ce24ede679e01e3ace5
[nhentai.git] / nhentai / parser.py
1 # coding: utf-8
2 from __future__ import unicode_literals, print_function
3
4 import os
5 import re
6 import threadpool
7 import requests
8 import time
9 from bs4 import BeautifulSoup
10 from tabulate import tabulate
11
12 import nhentai.constant as constant
13 from nhentai.logger import logger
14
15
16 session = requests.Session()
17 session.headers.update({
18     'Referer': constant.LOGIN_URL,
19     'User-Agent': 'nhentai command line client (https://github.com/RicterZ/nhentai)',
20 })
21
22
23 def request(method, url, **kwargs):
24     global session
25     if not hasattr(session, method):
26         raise AttributeError('\'requests.Session\' object has no attribute \'{0}\''.format(method))
27
28     return getattr(session, method)(url, proxies=constant.PROXY, verify=False, **kwargs)
29
30
31 def _get_csrf_token(content):
32     html = BeautifulSoup(content, 'html.parser')
33     csrf_token_elem = html.find('input', attrs={'name': 'csrfmiddlewaretoken'})
34     if not csrf_token_elem:
35         raise Exception('Cannot find csrf token to login')
36     return csrf_token_elem.attrs['value']
37
38
39 def login(username, password):
40     csrf_token = _get_csrf_token(request('get', url=constant.LOGIN_URL).text)
41     if os.getenv('DEBUG'):
42         logger.info('Getting CSRF token ...')
43
44     if os.getenv('DEBUG'):
45         logger.info('CSRF token is {}'.format(csrf_token))
46
47     login_dict = {
48         'csrfmiddlewaretoken': csrf_token,
49         'username_or_email': username,
50         'password': password,
51     }
52     resp = request('post', url=constant.LOGIN_URL, data=login_dict)
53
54     if 'You\'re loading pages way too quickly.' in resp.text:
55         csrf_token = _get_csrf_token(resp.text)
56         resp = request('post', url=resp.url, data={'csrfmiddlewaretoken': csrf_token, 'next': '/'})
57
58     if 'Invalid username/email or password' in resp.text:
59         logger.error('Login failed, please check your username and password')
60         exit(1)
61
62     if 'You\'re loading pages way too quickly.' in resp.text:
63         logger.error('You meet challenge insistently, please submit a issue'
64                      ' at https://github.com/RicterZ/nhentai/issues')
65         exit(2)
66
67
68 def login_parser():
69     html = BeautifulSoup(request('get', constant.FAV_URL).content, 'html.parser')
70     count = html.find('span', attrs={'class': 'count'})
71     if not count:
72         logger.error("Can't get your number of favorited doujins. Did the login failed?")
73         return []
74
75     count = int(count.text.strip('(').strip(')').replace(',', ''))
76     if count == 0:
77         logger.warning('No favorites found')
78         return []
79     pages = int(count / 25)
80
81     if pages:
82         pages += 1 if count % (25 * pages) else 0
83     else:
84         pages = 1
85
86     logger.info('You have %d favorites in %d pages.' % (count, pages))
87
88     if os.getenv('DEBUG'):
89         pages = 1
90
91     ret = []
92     doujinshi_id = re.compile('data-id="([\d]+)"')
93
94     def _callback(request, result):
95         ret.append(result)
96
97     # TODO: reduce threads number ...
98     thread_pool = threadpool.ThreadPool(3)
99
100     for page in range(1, pages + 1):
101         try:
102             logger.info('Getting doujinshi ids of page %d' % page)
103             resp = request('get', constant.FAV_URL + '?page=%d' % page).text
104             ids = doujinshi_id.findall(resp)
105             requests_ = threadpool.makeRequests(doujinshi_parser, ids, _callback)
106             [thread_pool.putRequest(req) for req in requests_]
107             thread_pool.wait()
108         except Exception as e:
109             logger.error('Error: %s, continue', str(e))
110
111     return ret
112
113
114 def doujinshi_parser(id_):
115     if not isinstance(id_, (int,)) and (isinstance(id_, (str,)) and not id_.isdigit()):
116         raise Exception('Doujinshi id({0}) is not valid'.format(id_))
117
118     id_ = int(id_)
119     logger.log(15, 'Fetching doujinshi information of id {0}'.format(id_))
120     doujinshi = dict()
121     doujinshi['id'] = id_
122     url = '{0}/{1}/'.format(constant.DETAIL_URL, id_)
123
124     try:
125         response = request('get', url)
126         if response.status_code in (200, ):
127             response = response.content
128         else:
129             logger.debug('Slow down and retry ({}) ...'.format(id_))
130             time.sleep(1)
131             return doujinshi_parser(str(id_))
132
133     except Exception as e:
134         logger.critical(str(e))
135         raise SystemExit
136
137     html = BeautifulSoup(response, 'html.parser')
138     doujinshi_info = html.find('div', attrs={'id': 'info'})
139
140     title = doujinshi_info.find('h1').text
141     subtitle = doujinshi_info.find('h2')
142
143     doujinshi['name'] = title
144     doujinshi['subtitle'] = subtitle.text if subtitle else ''
145
146     doujinshi_cover = html.find('div', attrs={'id': 'cover'})
147     img_id = re.search('/galleries/([\d]+)/cover\.(jpg|png)$', doujinshi_cover.a.img.attrs['data-src'])
148
149     ext = []
150     for i in html.find_all('div', attrs={'class': 'thumb-container'}):
151         _, ext_name = os.path.basename(i.img.attrs['data-src']).rsplit('.', 1)
152         ext.append(ext_name)
153
154     if not img_id:
155         logger.critical('Tried yo get image id failed')
156         exit(1)
157
158     doujinshi['img_id'] = img_id.group(1)
159     doujinshi['ext'] = ext
160
161     pages = 0
162     for _ in doujinshi_info.find_all('div', class_=''):
163         pages = re.search('([\d]+) pages', _.text)
164         if pages:
165             pages = pages.group(1)
166             break
167     doujinshi['pages'] = int(pages)
168
169     # gain information of the doujinshi
170     information_fields = doujinshi_info.find_all('div', attrs={'class': 'field-name'})
171     needed_fields = ['Characters', 'Artists', 'Language', 'Tags']
172     for field in information_fields:
173         field_name = field.contents[0].strip().strip(':')
174         if field_name in needed_fields:
175             data = [sub_field.contents[0].strip() for sub_field in
176                     field.find_all('a', attrs={'class': 'tag'})]
177             doujinshi[field_name.lower()] = ', '.join(data)
178
179     return doujinshi
180
181
182 def search_parser(keyword, page):
183     logger.debug('Searching doujinshis of keyword {0}'.format(keyword))
184     result = []
185     try:
186         response = request('get', url=constant.SEARCH_URL, params={'q': keyword, 'page': page}).content
187     except requests.ConnectionError as e:
188         logger.critical(e)
189         logger.warn('If you are in China, please configure the proxy to fu*k GFW.')
190         raise SystemExit
191
192     html = BeautifulSoup(response, 'html.parser')
193     doujinshi_search_result = html.find_all('div', attrs={'class': 'gallery'})
194     for doujinshi in doujinshi_search_result:
195         doujinshi_container = doujinshi.find('div', attrs={'class': 'caption'})
196         title = doujinshi_container.text.strip()
197         title = title if len(title) < 85 else title[:82] + '...'
198         id_ = re.search('/g/(\d+)/', doujinshi.a['href']).group(1)
199         result.append({'id': id_, 'title': title})
200     if not result:
201         logger.warn('Not found anything of keyword {}'.format(keyword))
202
203     return result
204
205
206 def __api_suspended_doujinshi_parser(id_):
207     if not isinstance(id_, (int,)) and (isinstance(id_, (str,)) and not id_.isdigit()):
208         raise Exception('Doujinshi id({0}) is not valid'.format(id_))
209
210     id_ = int(id_)
211     logger.log(15, 'Fetching information of doujinshi id {0}'.format(id_))
212     doujinshi = dict()
213     doujinshi['id'] = id_
214     url = '{0}/{1}'.format(constant.DETAIL_URL, id_)
215     i = 0
216     while 5 > i:
217         try:
218             response = request('get', url).json()
219         except Exception as e:
220             i += 1
221             if not i < 5:
222                 logger.critical(str(e))
223                 exit(1)
224             continue
225         break
226
227     doujinshi['name'] = response['title']['english']
228     doujinshi['subtitle'] = response['title']['japanese']
229     doujinshi['img_id'] = response['media_id']
230     doujinshi['ext'] = ''.join(map(lambda s: s['t'], response['images']['pages']))
231     doujinshi['pages'] = len(response['images']['pages'])
232
233     # gain information of the doujinshi
234     needed_fields = ['character', 'artist', 'language', 'tag']
235     for tag in response['tags']:
236         tag_type = tag['type']
237         if tag_type in needed_fields:
238             if tag_type == 'tag':
239                 if tag_type not in doujinshi:
240                     doujinshi[tag_type] = {}
241
242                 tag['name'] = tag['name'].replace(' ', '-')
243                 tag['name'] = tag['name'].lower()
244                 doujinshi[tag_type][tag['name']] = tag['id']
245             elif tag_type not in doujinshi:
246                 doujinshi[tag_type] = tag['name']
247             else:
248                 doujinshi[tag_type] += ', ' + tag['name']
249
250     return doujinshi
251
252
253 def __api_suspended_search_parser(keyword, page):
254     logger.debug('Searching doujinshis using keywords {0}'.format(keyword))
255     result = []
256     i = 0
257     while i < 5:
258         try:
259             response = request('get', url=constant.SEARCH_URL, params={'query': keyword, 'page': page}).json()
260         except Exception as e:
261             i += 1
262             if not i < 5:
263                 logger.critical(str(e))
264                 logger.warn('If you are in China, please configure the proxy to fu*k GFW.')
265                 exit(1)
266             continue
267         break
268
269     if 'result' not in response:
270         raise Exception('No result in response')
271
272     for row in response['result']:
273         title = row['title']['english']
274         title = title[:85] + '..' if len(title) > 85 else title
275         result.append({'id': row['id'], 'title': title})
276
277     if not result:
278         logger.warn('No results for keywords {}'.format(keyword))
279
280     return result
281
282
283 def print_doujinshi(doujinshi_list):
284     if not doujinshi_list:
285         return
286     doujinshi_list = [(i['id'], i['title']) for i in doujinshi_list]
287     headers = ['id', 'doujinshi']
288     logger.info('Search Result\n' +
289                 tabulate(tabular_data=doujinshi_list, headers=headers, tablefmt='rst'))
290
291
292 def __api_suspended_tag_parser(tag_id, max_page=1):
293     logger.info('Searching for doujinshi with tag id {0}'.format(tag_id))
294     result = []
295     response = request('get', url=constant.TAG_API_URL, params={'sort': 'popular', 'tag_id': tag_id}).json()
296     page = max_page if max_page <= response['num_pages'] else int(response['num_pages'])
297
298     for i in range(1, page + 1):
299         logger.info('Getting page {} ...'.format(i))
300
301         if page != 1:
302             response = request('get', url=constant.TAG_API_URL,
303                                params={'sort': 'popular', 'tag_id': tag_id}).json()
304     for row in response['result']:
305         title = row['title']['english']
306         title = title[:85] + '..' if len(title) > 85 else title
307         result.append({'id': row['id'], 'title': title})
308
309     if not result:
310         logger.warn('No results for tag id {}'.format(tag_id))
311
312     return result
313
314
315 def tag_parser(tag_name, max_page=1):
316     result = []
317     tag_name = tag_name.lower()
318     tag_name = tag_name.replace(' ', '-')
319
320     for p in range(1, max_page + 1):
321         logger.debug('Fetching page {0} for doujinshi with tag \'{1}\''.format(p, tag_name))
322         response = request('get', url='%s/%s?page=%d' % (constant.TAG_URL, tag_name, p)).content
323
324         html = BeautifulSoup(response, 'html.parser')
325         doujinshi_items = html.find_all('div', attrs={'class': 'gallery'})
326         if not doujinshi_items:
327             logger.error('Cannot find doujinshi id of tag \'{0}\''.format(tag_name))
328             return
329
330         for i in doujinshi_items:
331             doujinshi_id = i.a.attrs['href'].strip('/g')
332             doujinshi_title = i.a.text.strip()
333             doujinshi_title = doujinshi_title if len(doujinshi_title) < 85 else doujinshi_title[:82] + '...'
334             result.append({'title': doujinshi_title, 'id': doujinshi_id})
335
336     if not result:
337         logger.warn('No results for tag \'{}\''.format(tag_name))
338
339     return result
340
341
342 if __name__ == '__main__':
343     print(doujinshi_parser("32271"))