Update Legimi panel
[redakcja.git] / src / depot / publishers / legimi.py
1 from datetime import date
2 import re
3 from urllib.parse import urljoin
4 from django.conf import settings
5 from django.utils.html import escape
6 from django.utils.safestring import mark_safe
7 from librarian.functions import lang_code_3to2
8 from librarian.builders import EpubBuilder, MobiBuilder
9 from librarian.covers.marquise import MarquiseCover, LabelMarquiseCover
10 from lxml import html
11 from catalogue.models import Audience, Thema
12 from .base import BasePublisher
13
14
15 class Legimi(BasePublisher):
16     BASE_URL = 'https://wydawca.legimi.pl'
17     UPLOAD_URL = BASE_URL + '/upload/start'
18     CREATE_URL = BASE_URL + '/publishers/publications/create'
19     EDIT_URL = BASE_URL + '/publishers/publications/edit/%s'
20     EDIT_FILES_URL = BASE_URL + '/publishers/publications/editfiles/%s'
21     EDIT_SALE_URL = BASE_URL + '/publishers/publications/editsale/%s'
22
23     CATEGORIES = {
24         'Dla dzieci i młodzieży': 94,
25         'Książki dla dzieci': 15,
26         'Literatura młodzieżowa': 24,
27         'Kryminał': 29,
28         'Kryminał klasyczny': 31,
29         'Kryminał współczesny': 32,
30         'Kryminał historyczny': 30,
31         'default': 8886,
32         'Edukacja': 10,
33         'Słowniki i leksykony': 14,
34         'Encyklopedie': 13,
35         'Lektury': 11,
36         'Starożytność': 80,
37         'Barok': 83,
38         'Oświecenie': 84,
39         'Dwudziestolecie międzywojenne': 88,
40         'Średniowiecze': 81,
41         'Współczesność': 90,
42         'Modernizm': 87,
43         'Pozytywizm': 86,
44         'Renesans': 82,
45         'Romantyzm': 85,
46         'Młoda Polska': 89,
47         'Podręczniki': 52,
48         'Fantastyka i sci-fi': 25,
49         'Fantastyka': 26,
50         'Science fiction': 27,
51         'Języki obce': 59,
52         'Antyki i kolekcjonerstwo': 53,
53         'Astrologia i wróżbiarstwo': 54,
54         'Zdrowie i rodzina': 57,
55         'Hobby': 55,
56         'Medycyna i zdrowie': 58,
57         'Psychologiczne': 78,
58         'Styl': 56,
59         'Humanistyka': 97,
60         'Kultura i sztuka': 64,
61         'Film': 66,
62         'Muzyka': 65,
63         'Eseje literackie': 49,
64         'Historia': 60,
65         'Styl życia': 73,
66         'Wakacje i podróże': 69,
67         'Dla mężczyzn': 79,
68         'Sport': 76,
69         'Obyczajowe i romanse': 93,
70         'Humor': 68,
71         'Obyczajowe': 35,
72         'Powieść': 41,
73         'Powieść przygodowa': 42,
74         'Współczesna powieść przygodowa': 44,
75         'Historyczna powieść przygodowa': 43,
76         'Powieść historyczna': 46,
77         'Powieść psychologiczna': 47,
78         'Powieść religijna': 45,
79         'Romans': 36,
80         'Romans klasyczny': 38,
81         'Romans współczesny': 39,
82         'Literatura erotyczna': 40,
83         'Romans historyczny': 37,
84         'Dla kobiet': 77,
85         'Sensacja, thriller, horror': 91,
86         'Horror': 28,
87         'Sensacja': 33,
88         'Thriller': 34,
89         'Aktualności': 70,
90         'Czasopisma': 71,
91         'Literatura faktu, reportaże, biografie': 92,
92         'Literatura faktu': 16,
93         'Biografie': 17,
94         'Publicystyka': 20,
95         'Dzienniki': 19,
96         'Dokument, esej': 18,
97         'Historia literatury i krytyka literacka': 23,
98         'Literatura popularnonaukowa': 22,
99         'Reportaż': 21,
100         'Społeczno-polityczne': 72,
101         'Poezja i dramat': 95,
102         'Dramat': 48,
103         'Poezja': 50,
104         'Religia i duchowość': 51,
105         'Nauka i nowe technologie': 98,
106         'Nauka i technika': 61,
107         'Nauki ścisłe': 62,
108         'Nauki humanistyczne': 63,
109         'Technologia i Internet': 75,
110         'Specjalistyczne': 99,
111         'Biznes i finanse': 1,
112         'Ekonomia': 5,
113         'Finanse': 6,
114         'Zarządzanie': 3,
115         'Marketing': 2,
116         'Rozwój osobisty': 7,
117         'Kariera i sukces zawodowy': 8,
118         'Psychologia, motywacja': 9,
119         'PR': 4,
120         'Prawo': 67,
121         'Branżowe': 74,
122     }
123
124     def login(self):
125         r = self._session.get(self.BASE_URL)
126         h = html.fromstring(r.text)
127         form = h.findall('.//form')[0]
128         login_url = urljoin(r.url, form.action)
129         data = {e.name: e.value for e in form.findall('.//input')}
130         data['Login'] = self.username
131         data['Password'] = self.password
132         self._session.post(login_url, data=data)
133
134     def can_publish(self, site, book):
135         d = {
136             'errors': [],
137             'warnings': [],
138             'info': []
139         }
140         try:
141             meta = book.wldocument(librarian2=True).meta
142         except:
143             d['errors'].append('Nieprawidłowy dokument.')
144             return d
145         thema = self.get_thema(meta)
146         if thema:
147             d['info'].append(mark_safe(
148                 "w kategorii " + ", ".join(
149                     "<b><tt>{code}</tt></b>".format(code=escape(t))
150                     for t in thema
151                 )
152             ))
153             if not meta.thema_main:
154                 d['warnings'].append('Brak głównej kategorii Thema')
155         else:
156             d['errors'].append('Brak kategorii Thema.')
157         return d
158
159     def list(self):
160         return self.session.get('https://wydawca.legimi.com/publishers/publications')
161
162     def upload(self, content):
163         response = self.session.post(
164             self.UPLOAD_URL,
165             files={
166                 "files": content,
167             })
168         model = response.json()['model']
169         return {
170             "name": model['Name'],
171             "token": model['Token'],
172             "url": model['Url'],
173         }
174
175     def get_thema(self, meta):
176         thema = []
177         if meta.thema_main:
178             thema.append(meta.thema_main)
179         thema.extend(meta.thema)
180
181         thema.extend(
182             Audience.objects.filter(code__in=meta.audiences).exclude(
183                 thema=None).values_list('thema', flat=True)
184         )
185         thema = [Thema.standardize(t) for t in thema]
186         return thema
187
188     def send_book(self, site_book_publish, changes=None):
189         site_book = site_book_publish.site_book
190         site = site_book.site
191         book = site_book.book
192         wlbook = book.wldocument(librarian2=True, changes=changes)
193         meta = wlbook.meta
194
195         cover = LabelMarquiseCover(meta, width=1200).output_file()
196         texts = site.get_texts()
197         epub_file = EpubBuilder(
198             cover=MarquiseCover,
199             fundraising=texts,
200             base_url='file://' + book.gallery_path() + '/'
201         ).build(wlbook).get_file()
202         mobi_file = MobiBuilder(
203             cover=MarquiseCover,
204             fundraising=texts,
205             base_url='file://' + book.gallery_path() + '/'
206         ).build(wlbook).get_file()
207
208         book_data = {
209             "Title": meta.title,
210             "Author": ", ".join(p.readable() for p in meta.authors),
211             "Year": str(date.today().year),
212
213             'GenreId': str(self.get_genre(wlbook)),
214             'themaCategories': ';'.join(self.get_thema(meta)),
215             'thema-search': '',
216             'Isbn': '',
217             'LanguageLocale': lang_code_3to2(meta.language),
218
219             'Description': self.get_description(wlbook, site.description_add),
220         }
221         if meta.isbn_html:
222             isbn = meta.isbn_html
223             if isbn.upper().startswith(('ISBN ', 'ISBN-')):
224                 isbn = isbn[5:]
225             isbn = isbn.strip()
226             book_data['Isbn'] = isbn
227
228         files_data = {}
229
230         cover_data = self.upload(
231             (meta.url.slug + '.jpg', cover.get_file(), 'image/jpeg')
232         )
233         book_data.update({
234             "Cover.Name": cover_data['name'],
235             "Cover.Token": cover_data['token'],
236             "Cover.Url": cover_data['url'],
237         })
238
239         epub_data = self.upload(
240             (meta.url.slug + '.epub', epub_file, 'application/epub+zip')
241         )
242         files_data.update({
243             'BookEpub.Token': epub_data['token'],
244             'BookEpub.Name': epub_data['name'],
245             'SampleEpubType': 'Generation',
246         })
247
248         mobi_data = self.upload(
249             (meta.url.slug + '.mobi', mobi_file, 'application/x-mobipocket-ebook')
250         )
251         files_data.update({
252             'BookMobi.Token': mobi_data['token'],
253             'BookMobi.Name': mobi_data['name'],
254         })
255
256         if site_book.external_id:
257             self.edit(
258                 site_book.external_id,
259                 book_data
260             )
261             self.edit_files(
262                 site_book.external_id,
263                 files_data
264             )
265         else:
266             legimi_id = self.create_book(book_data, files_data)
267             if legimi_id:
268                 site_book.external_id = legimi_id
269                 site_book.save(update_fields=['external_id'])
270
271         self.edit_sale(site_book)
272
273     def get_genre(self, wlbook):
274         if wlbook.meta.legimi and wlbook.meta.legimi in self.CATEGORIES:
275             return self.CATEGORIES[wlbook.meta.legimi]
276         for epoch in wlbook.meta.epochs:
277             if epoch in self.CATEGORIES:
278                 return self.CATEGORIES[epoch]
279         return self.CATEGORIES['Lektury']
280
281     def create_book(self, book_data, files_data):
282         data = {
283             'createValidationTrue': 'true',
284             'PublisherId': self.publisher_handle,
285             'IsLibraryPass': 'False',
286
287             'SamplesGenerationType': 'Quantity',
288             'SamplesGenerationPercent': '10',
289
290             'EnterToTheMarketType': 'No',
291             'EnterToTheMarketDate': '',
292             'HidingDate': '',
293             'SalesNoLimitOption': 'false',
294             'SalesNoLimitKindle': 'false',
295             'SalesInStoreEbookGrossValue': '0,00',
296             'SalesPromotion': 'False',
297             'SalesPromotionGrossValue': '0,00',
298             'SalesPromotionDatesRange.DateStart': '',
299             'SalesPromotionDatesRange.DateEnd': '',
300         }
301
302         for form in 'Epub', 'Mobi', 'Pdf':
303             data.update({
304                 f'Book{form}.Token': '',
305                 f'Book{form}.Name': '',
306                 f'Book{form}.StorageName': '',
307                 f'Book{form}.Order': '',
308
309                 f'Sample{form}Type': 'Files',
310                 f'Sample{form}.Token': '',
311                 f'Sample{form}.Name': '',
312                 f'Sample{form}.StorageName': '',
313                 f'Sample{form}.Order': '',
314             })
315
316         data.update(book_data)
317         data.update(files_data)
318
319         response = self.session.post(self.CREATE_URL, data=data)
320         m = re.search(r'/(\d+)$', response.url)
321         assert m is not None, 'Legimi.create_book: ' + response.text
322         return m.group(1)
323
324     def edit(self, legimi_id, data):
325         current = {
326             'ValidationTrue': 'true',
327             'Id': legimi_id
328         }
329
330         current.update(data)
331
332         self.session.post(
333             self.EDIT_URL % legimi_id,
334             data=current
335         )
336
337     def edit_files(self, legimi_id, files_data):
338         current = {
339             'ValidationTrue': 'true',
340             'Id': legimi_id,
341             'SamplesGenerationType': 'Quantity',
342             'SamplesGenerationPercent': '10',
343         }
344
345         for form in 'Epub', 'Mobi', 'Pdf':
346             current.update({
347                 f'Book{form}.Token': '',
348                 f'Book{form}.Name': '',
349                 f'Book{form}.StorageName': '',
350                 f'Book{form}.Order': '',
351
352                 f'Sample{form}.Type': 'Files',
353                 f'Sample{form}.Token': '',
354                 f'Sample{form}.Name': '',
355                 f'Sample{form}.StorageName': '',
356                 f'Sample{form}.Order': '',
357             })
358
359         current.update(files_data)
360
361         response = self.session.post(
362             self.EDIT_FILES_URL % legimi_id,
363             data=current
364         )
365
366     def edit_sale(self, site_book):
367         book = site_book.book
368         assert site_book.external_id
369
370         words = book.wldocument(librarian2=True).get_statistics()['total']['words_with_fn']
371
372         price = settings.LEGIMI_SMALL_PRICE
373         if words > settings.LEGIMI_SMALL_WORDS:
374             price = settings.LEGIMI_BIG_PRICE
375
376         abo = 'true' if words > settings.LEGIMI_BIG_WORDS else 'false'
377
378         data = {
379             'ValidationTrue': 'true',
380             'Id': site_book.external_id,
381             'SalesPromotionId': "0",
382             'IsLibraryPass': "False",
383             'OriginalEnterToTheMarketType': "No",
384             'OriginalHidingDate': "",
385             'OriginalEnterToTheMarketDate': "",
386             'EnterToTheMarketType': "Yes",
387             'EnterToTheMarketDate': "",
388             'HidingDate': "",
389             'SalesNoLimitOption': abo,
390             'SalesNoLimitKindle': abo,
391             'SalesInStoreEbookGrossValue': f'{price},00',
392             'SalesPromotion': "False",
393             'SalesPromotionGrossValue': "0,00",
394             'SalesPromotionDatesRange.DateStart': "",
395             'SalesPromotionDatesRange.DateEnd': "",
396         }
397
398         self.session.post(
399             self.EDIT_SALE_URL % site_book.external_id,
400             data=data
401         )