Add audience and thema data to catalogue. Also: nicer cover view.
[redakcja.git] / src / depot / legimi.py
1 from datetime import date
2 import re
3 from django.conf import settings
4 from librarian.functions import lang_code_3to2
5 from librarian.html import transform_abstrakt
6 from librarian.builders import EpubBuilder, MobiBuilder
7 from librarian.covers.marquise import MarquiseCover, LabelMarquiseCover
8 import requests
9 from slugify import slugify
10
11
12
13 fundraising=[
14             "Książka, którą czytasz, pochodzi z <a href=\"https://wolnelektury.pl/\">Wolnych Lektur</a>. Naszą misją jest wspieranie dzieciaków w dostępie do lektur szkolnych oraz zachęcanie ich do czytania. Miło Cię poznać!",
15             "Podoba Ci się to, co robimy? Jesteśmy organizacją pożytku publicznego. Wesprzyj Wolne Lektury drobną wpłatą: <a href=\"https://wolnelektury.pl/towarzystwo/\">wolnelektury.pl/towarzystwo/</a>",
16             "Przyjaciele Wolnych Lektur otrzymują dostęp do prapremier wcześniej niż inni. Zadeklaruj stałą wpłatę i dołącz do Towarzystwa Przyjaciół Wolnych Lektur: <a href=\"https://wolnelektury.pl/towarzystwo/\">wolnelektury.pl/towarzystwo/</a>",
17             "Informacje o nowościach w naszej bibliotece w Twojej skrzynce mailowej? Nic prostszego, zapisz się do newslettera. Kliknij, by pozostawić swój adres e-mail: <a href=\"https://wolnelektury.pl/newsletter/zapisz-sie/\">wolnelektury.pl/newsletter/zapisz-sie/</a>",
18             "Przekaż 1% podatku na Wolne Lektury.<br/>\nKRS: 0000070056<br/>\nNazwa organizacji: Fundacja Nowoczesna Polska<br/>\nKażda wpłacona kwota zostanie przeznaczona na rozwój Wolnych Lektur."
19 ]
20
21 description_add = '<p>Książkę polecają <a href="https://wolnelektury.pl">Wolne Lektury</a> — najpopularniejsza biblioteka on-line.</p>'
22
23
24 class Legimi:
25     #BASE_URL = 'https://wydawca.legimi.com'
26     BASE_URL = 'https://panel.legimi.pl'
27     LOGIN_URL = BASE_URL + '/publishers/membership'
28     UPLOAD_URL = BASE_URL + '/administration/upload/start'
29     CREATE_URL = BASE_URL + '/publishers/publications/create'
30     EDIT_URL = BASE_URL + '/publishers/publications/edit/%s'
31     EDIT_FILES_URL = BASE_URL + '/publishers/publications/editfiles/%s'
32     EDIT_SALE_URL = BASE_URL + '/publishers/publications/editsale/%s'
33
34     CATEGORIES = {
35         'Dla dzieci i młodzieży': 94,
36         'Książki dla dzieci': 15,
37         'Literatura młodzieżowa': 24,
38         'Kryminał': 29,
39         'Kryminał klasyczny': 31,
40         'Kryminał współczesny': 32,
41         'Kryminał historyczny': 30,
42         'default': 8886,
43         'Edukacja': 10,
44         'Słowniki i leksykony': 14,
45         'Encyklopedie': 13,
46         'Lektury': 11,
47         'Starożytność': 80,
48         'Barok': 83,
49         'Oświecenie': 84,
50         'Dwudziestolecie międzywojenne': 88,
51         'Średniowiecze': 81,
52         'Współczesność': 90,
53         'Modernizm': 87,
54         'Pozytywizm': 86,
55         'Renesans': 82,
56         'Romantyzm': 85,
57         'Młoda Polska': 89,
58         'Podręczniki': 52,
59         'Fantastyka i sci-fi': 25,
60         'Fantastyka': 26,
61         'Science fiction': 27,
62         'Języki obce': 59,
63         'Antyki i kolekcjonerstwo': 53,
64         'Astrologia i wróżbiarstwo': 54,
65         'Zdrowie i rodzina': 57,
66         'Hobby': 55,
67         'Medycyna i zdrowie': 58,
68         'Psychologiczne': 78,
69         'Styl': 56,
70         'Humanistyka': 97,
71         'Kultura i sztuka': 64,
72         'Film': 66,
73         'Muzyka': 65,
74         'Eseje literackie': 49,
75         'Historia': 60,
76         'Styl życia': 73,
77         'Wakacje i podróże': 69,
78         'Dla mężczyzn': 79,
79         'Sport': 76,
80         'Obyczajowe i romanse': 93,
81         'Humor': 68,
82         'Obyczajowe': 35,
83         'Powieść': 41,
84         'Powieść przygodowa': 42,
85         'Współczesna powieść przygodowa': 44,
86         'Historyczna powieść przygodowa': 43,
87         'Powieść historyczna': 46,
88         'Powieść psychologiczna': 47,
89         'Powieść religijna': 45,
90         'Romans': 36,
91         'Romans klasyczny': 38,
92         'Romans współczesny': 39,
93         'Literatura erotyczna': 40,
94         'Romans historyczny': 37,
95         'Dla kobiet': 77,
96         'Sensacja, thriller, horror': 91,
97         'Horror': 28,
98         'Sensacja': 33,
99         'Thriller': 34,
100         'Aktualności': 70,
101         'Czasopisma': 71,
102         'Literatura faktu, reportaże, biografie': 92,
103         'Literatura faktu': 16,
104         'Biografie': 17,
105         'Publicystyka': 20,
106         'Dzienniki': 19,
107         'Dokument, esej': 18,
108         'Historia literatury i krytyka literacka': 23,
109         'Literatura popularnonaukowa': 22,
110         'Reportaż': 21,
111         'Społeczno-polityczne': 72,
112         'Poezja i dramat': 95,
113         'Dramat': 48,
114         'Poezja': 50,
115         'Religia i duchowość': 51,
116         'Nauka i nowe technologie': 98,
117         'Nauka i technika': 61,
118         'Nauki ścisłe': 62,
119         'Nauki humanistyczne': 63,
120         'Technologia i Internet': 75,
121         'Specjalistyczne': 99,
122         'Biznes i finanse': 1,
123         'Ekonomia': 5,
124         'Finanse': 6,
125         'Zarządzanie': 3,
126         'Marketing': 2,
127         'Rozwój osobisty': 7,
128         'Kariera i sukces zawodowy': 8,
129         'Psychologia, motywacja': 9,
130         'PR': 4,
131         'Prawo': 67,
132         'Branżowe': 74,
133     }
134     
135     def __init__(self, username, password, publisher_id):
136         self.username = username
137         self.password = password
138         self.publisher_id = publisher_id
139         self._session = None
140
141     @property
142     def session(self):
143         if self._session is None:
144             session = requests.Session()
145             response = session.post(
146                 self.LOGIN_URL,
147                 data={
148                     'ValidationTrue': 'true',
149                     'UserName': self.username,
150                     'Password': self.password,
151                 })
152             self._session = session
153         return self._session
154         
155     def list(self):
156         return self.session.get('https://wydawca.legimi.com/publishers/publications')
157         
158     def upload(self, content):
159         response = self.session.post(
160             self.UPLOAD_URL,
161             files={
162                 "files": content,
163             })
164         model = response.json()['model']
165         return {
166             "name": model['Name'],
167             "token": model['Token'],
168             "url": model['Url'],
169         }
170
171     def send_book(self, book, changes=None):
172         wlbook = book.wldocument(librarian2=True, changes=changes)
173         meta = wlbook.meta
174
175         cover = LabelMarquiseCover(meta, width=1200).output_file()
176         epub_file = EpubBuilder(
177             cover=MarquiseCover,
178             fundraising=fundraising,
179             base_url='file://' + book.gallery_path() + '/'
180         ).build(wlbook).get_file()
181         mobi_file = MobiBuilder(
182             cover=MarquiseCover,
183             fundraising=fundraising,
184             base_url='file://' + book.gallery_path() + '/'
185         ).build(wlbook).get_file()
186
187         book_data = {
188             "Title": meta.title,
189             "Author": ", ".join(p.readable() for p in meta.authors),
190             "Year": str(date.today().year),
191
192             'GenreId': str(self.get_genre(wlbook)),
193             'themaCategories': ';'.join(meta.thema),
194             'thema-search': '',
195             'Isbn': '',
196             'LanguageLocale': lang_code_3to2(meta.language),
197
198             'Description': self.get_description(wlbook),
199         }
200         if meta.isbn_html:
201             isbn = meta.isbn_html
202             if isbn.upper().startswith(('ISBN ', 'ISBN-')):
203                 isbn = isbn[5:]
204             isbn = isbn.strip()
205             book_data['Isbn'] = isbn
206
207         files_data = {}
208
209         cover_data = self.upload(
210             (meta.url.slug + '.jpg', cover.get_file(), 'image/jpeg')
211         )
212         book_data.update({
213             "Cover.Name": cover_data['name'],
214             "Cover.Token": cover_data['token'],
215             "Cover.Url": cover_data['url'],
216         })
217
218         epub_data = self.upload(
219             (meta.url.slug + '.epub', epub_file, 'application/epub+zip')
220         )
221         files_data.update({
222             'BookEpub.Token': epub_data['token'],
223             'BookEpub.Name': epub_data['name'],
224             'SampleEpubType': 'Generation',
225         })
226
227         mobi_data = self.upload(
228             (meta.url.slug + '.mobi', mobi_file, 'application/x-mobipocket-ebook')
229         )
230         files_data.update({
231             'BookMobi.Token': mobi_data['token'],
232             'BookMobi.Name': mobi_data['name'],
233         })
234         
235         if book.legimi_id:
236             self.edit(
237                 book.legimi_id,
238                 book_data
239             )
240             self.edit_files(
241                 book.legimi_id,
242                 files_data
243             )
244         else:
245             legimi_id = self.create_book(book_data, files_data)
246             if legimi_id:
247                 book.legimi_id = legimi_id
248                 book.save(update_fields=['legimi_id'])
249
250     def get_description(self, wlbook):
251         description = ''
252         abstract = wlbook.tree.find('.//abstrakt')
253         if abstract is not None:
254             description = transform_abstrakt(abstract)
255         description += description_add
256         description += '<p>'
257         description += ', '.join(
258             '<a href="https://wolnelektury.pl/katalog/autor/{}/">{}</a>'.format(
259                 slugify(p.readable()),
260                 p.readable(),
261             )
262             for p in wlbook.meta.authors
263         ) + '<br>'
264         description += '<a href="https://wolnelektury.pl/katalog/lektura/{}/">{}</a><br>'.format(
265             wlbook.meta.url.slug,
266             wlbook.meta.title
267         )
268         if wlbook.meta.translators:
269             description += 'tłum. ' + ', '.join(p.readable() for p in wlbook.meta.translators) + '<br>'
270         description += 'Epoka: ' + ', '.join(
271             '<a href="https://wolnelektury.pl/katalog/epoka/{}/">{}</a>'.format(
272                 slugify(p),
273                 p,
274             )
275             for p in wlbook.meta.epochs
276         ) + ' '
277         description += 'Rodzaj: ' + ', '.join(
278             '<a href="https://wolnelektury.pl/katalog/rodzaj/{}/">{}</a>'.format(
279                 slugify(p),
280                 p,
281             )
282             for p in wlbook.meta.kinds
283         ) + ' '
284         description += 'Gatunek: ' + ', '.join(
285             '<a href="https://wolnelektury.pl/katalog/gatunek/{}/">{}</a>'.format(
286                 slugify(p),
287                 p,
288             )
289             for p in wlbook.meta.genres
290         ) + '</p>'
291
292         if wlbook.meta.audience:
293             description += '<p><em>{}</em> to lektura szkolna.'.format(wlbook.meta.title)
294             if wlbook.tree.find('//pe') is not None:
295                 description += '<br>Ebook <em>{title}</em> zawiera przypisy opracowane specjalnie dla uczennic i uczniów {school}.'.format(
296                     title=wlbook.meta.title,
297                     school='szkoły podstawowej' if wlbook.meta.audience == 'SP' else 'liceum i technikum'
298                 )
299             description += '</p>'
300         return description
301
302     def get_genre(self, wlbook):
303         if wlbook.meta.legimi and wlbook.meta.legimi in self.CATEGORIES:
304             return self.CATEGORIES[wlbook.meta.legimi]
305         for epoch in wlbook.meta.epochs:
306             if epoch in self.CATEGORIES:
307                 return self.CATEGORIES[epoch]
308         return self.CATEGORIES['Lektury']
309     
310     def create_book(self, book_data, files_data):
311         data = {
312             'createValidationTrue': 'true',
313             'PublisherId': self.publisher_id,#3609954
314             'IsLibraryPass': 'False',
315
316             'SamplesGenerationType': 'Quantity',
317             'SamplesGenerationPercent': '10',
318
319             'EnterToTheMarketType': 'No',
320             'EnterToTheMarketDate': '',
321             'HidingDate': '',
322             'SalesNoLimitOption': 'false',
323             'SalesNoLimitKindle': 'false',
324             'SalesInStoreEbookGrossValue': '0,00',
325             'SalesPromotion': 'False',
326             'SalesPromotionGrossValue': '0,00',
327             'SalesPromotionDatesRange.DateStart': '',
328             'SalesPromotionDatesRange.DateEnd': '',
329         }
330
331         for form in 'Epub', 'Mobi', 'Pdf':
332             data.update({
333                 f'Book{form}.Token': '',
334                 f'Book{form}.Name': '',
335                 f'Book{form}.StorageName': '',
336                 f'Book{form}.Order': '',
337
338                 f'Sample{form}Type': 'Files',
339                 f'Sample{form}.Token': '',
340                 f'Sample{form}.Name': '',
341                 f'Sample{form}.StorageName': '',
342                 f'Sample{form}.Order': '',
343             })
344
345         data.update(book_data)
346         data.update(files_data)
347
348         response = self.session.post(self.CREATE_URL, data=data)
349         m = re.search(r'/(\d+)$', response.url)
350         if m is not None:
351             return m.group(1)
352
353     def edit(self, legimi_id, data):
354         current = {
355             'ValidationTrue': 'true',
356             'Id': legimi_id
357         }
358
359         current.update(data)
360         
361         self.session.post(
362             self.EDIT_URL % legimi_id,
363             data=current
364         )
365
366     def edit_files(self, legimi_id, files_data):
367         current = {
368             'ValidationTrue': 'true',
369             'Id': legimi_id,
370             'SamplesGenerationType': 'Quantity',
371             'SamplesGenerationPercent': '10',
372         }
373
374         for form in 'Epub', 'Mobi', 'Pdf':
375             current.update({
376                 f'Book{form}.Token': '',
377                 f'Book{form}.Name': '',
378                 f'Book{form}.StorageName': '',
379                 f'Book{form}.Order': '',
380
381                 f'Sample{form}.Type': 'Files',
382                 f'Sample{form}.Token': '',
383                 f'Sample{form}.Name': '',
384                 f'Sample{form}.StorageName': '',
385                 f'Sample{form}.Order': '',
386             })
387
388         current.update(files_data)
389  
390         response = self.session.post(
391             self.EDIT_FILES_URL % legimi_id,
392             data=current
393         )
394
395     def edit_sale(self, book):
396         assert book.legimi_id
397
398         words = book.wldocument().get_statistics()['total']['words_with_fn']
399
400         price = settings.LEGIMI_SMALL_PRICE
401         if words > settings.LEGIMI_SMALL_WORDS:
402             price = settings.LEGIMI_BIG_PRICE
403
404         abo = 'true' if words > settings.LEGIMI_BIG_WORDS else 'false'
405
406         data = {
407             'ValidationTrue': 'true',
408             'Id': book.legimi_id,
409             'SalesPromotionId': "0",
410             'IsLibraryPass': "False",
411             'OriginalEnterToTheMarketType': "No",
412             'OriginalHidingDate': "",
413             'OriginalEnterToTheMarketDate': "",
414             'EnterToTheMarketType': "Yes",
415             'EnterToTheMarketDate': "",
416             'HidingDate': "",
417             'SalesNoLimitOption': abo,
418             'SalesNoLimitKindle': abo,
419             'SalesInStoreEbookGrossValue': f'{price},00',
420             'SalesPromotion': "False",
421             'SalesPromotionGrossValue': "0,00",
422             'SalesPromotionDatesRange.DateStart': "",
423             'SalesPromotionDatesRange.DateEnd': "",
424         }
425
426         self.session.post(
427             self.EDIT_SALE_URL % book.legimi_id,
428             data=data
429         )
430
431
432 legimi = Legimi(
433     settings.LEGIMI_USERNAME,
434     settings.LEGIMI_PASSWORD,
435     settings.LEGIMI_PUBLISHER_ID,
436 )