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