Fix for Legimi.
[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             'Isbn': '',
194             'LanguageLocale': lang_code_3to2(meta.language),
195
196             'Description': self.get_description(wlbook),
197         }
198         if meta.isbn_html:
199             isbn = meta.isbn_html
200             if isbn.upper().startswith(('ISBN ', 'ISBN-')):
201                 isbn = isbn[5:]
202             isbn = isbn.strip()
203             book_data['Isbn'] = isbn
204
205         files_data = {}
206
207         cover_data = self.upload(
208             (meta.url.slug + '.jpg', cover.get_file(), 'image/jpeg')
209         )
210         book_data.update({
211             "Cover.Name": cover_data['name'],
212             "Cover.Token": cover_data['token'],
213             "Cover.Url": cover_data['url'],
214         })
215
216         epub_data = self.upload(
217             (meta.url.slug + '.epub', epub_file, 'application/epub+zip')
218         )
219         files_data.update({
220             'BookEpub.Token': epub_data['token'],
221             'BookEpub.Name': epub_data['name'],
222             'SampleEpubType': 'Generation',
223         })
224
225         mobi_data = self.upload(
226             (meta.url.slug + '.mobi', mobi_file, 'application/x-mobipocket-ebook')
227         )
228         files_data.update({
229             'BookMobi.Token': mobi_data['token'],
230             'BookMobi.Name': mobi_data['name'],
231         })
232         
233         if book.legimi_id:
234             self.edit(
235                 book.legimi_id,
236                 book_data
237             )
238             self.edit_files(
239                 book.legimi_id,
240                 files_data
241             )
242         else:
243             legimi_id = self.create_book(book_data, files_data)
244             if legimi_id:
245                 book.legimi_id = legimi_id
246                 book.save(update_fields=['legimi_id'])
247
248     def get_description(self, wlbook):
249         description = ''
250         abstract = wlbook.tree.find('.//abstrakt')
251         if abstract is not None:
252             description = transform_abstrakt(abstract)
253         description += description_add
254         description += '<p>'
255         description += ', '.join(
256             '<a href="https://wolnelektury.pl/katalog/autor/{}/">{}</a>'.format(
257                 slugify(p.readable()),
258                 p.readable(),
259             )
260             for p in wlbook.meta.authors
261         ) + '<br>'
262         description += '<a href="https://wolnelektury.pl/katalog/lektura/{}/">{}</a><br>'.format(
263             wlbook.meta.url.slug,
264             wlbook.meta.title
265         )
266         if wlbook.meta.translators:
267             description += 'tłum. ' + ', '.join(p.readable() for p in wlbook.meta.translators) + '<br>'
268         description += 'Epoka: ' + ', '.join(
269             '<a href="https://wolnelektury.pl/katalog/epoka/{}/">{}</a>'.format(
270                 slugify(p),
271                 p,
272             )
273             for p in wlbook.meta.epochs
274         ) + ' '
275         description += 'Rodzaj: ' + ', '.join(
276             '<a href="https://wolnelektury.pl/katalog/rodzaj/{}/">{}</a>'.format(
277                 slugify(p),
278                 p,
279             )
280             for p in wlbook.meta.kinds
281         ) + ' '
282         description += 'Gatunek: ' + ', '.join(
283             '<a href="https://wolnelektury.pl/katalog/gatunek/{}/">{}</a>'.format(
284                 slugify(p),
285                 p,
286             )
287             for p in wlbook.meta.genres
288         ) + '</p>'
289
290         if wlbook.meta.audience:
291             description += '<p><em>{}</em> to lektura szkolna.'.format(wlbook.meta.title)
292             if wlbook.tree.find('//pe') is not None:
293                 description += '<br>Ebook <em>{title}</em> zawiera przypisy opracowane specjalnie dla uczennic i uczniów {school}.'.format(
294                     title=wlbook.meta.title,
295                     school='szkoły podstawowej' if wlbook.meta.audience == 'SP' else 'liceum i technikum'
296                 )
297             description += '</p>'
298         return description
299
300     def get_genre(self, wlbook):
301         if wlbook.meta.legimi and wlbook.meta.legimi in self.CATEGORIES:
302             return self.CATEGORIES[wlbook.meta.legimi]
303         for epoch in wlbook.meta.epochs:
304             if epoch in self.CATEGORIES:
305                 return self.CATEGORIES[epoch]
306         return self.CATEGORIES['Lektury']
307     
308     def create_book(self, book_data, files_data):
309         data = {
310             'createValidationTrue': 'true',
311             'PublisherId': self.publisher_id,#3609954
312             'IsLibraryPass': 'False',
313
314             'SamplesGenerationType': 'Quantity',
315             'SamplesGenerationPercent': '10',
316
317             'EnterToTheMarketType': 'No',
318             'EnterToTheMarketDate': '',
319             'HidingDate': '',
320             'SalesNoLimitOption': 'false',
321             'SalesNoLimitKindle': 'false',
322             'SalesInStoreEbookGrossValue': '0,00',
323             'SalesPromotion': 'False',
324             'SalesPromotionGrossValue': '0,00',
325             'SalesPromotionDatesRange.DateStart': '',
326             'SalesPromotionDatesRange.DateEnd': '',
327         }
328
329         for form in 'Epub', 'Mobi', 'Pdf':
330             data.update({
331                 f'Book{form}.Token': '',
332                 f'Book{form}.Name': '',
333                 f'Book{form}.StorageName': '',
334                 f'Book{form}.Order': '',
335
336                 f'Sample{form}Type': 'Files',
337                 f'Sample{form}.Token': '',
338                 f'Sample{form}.Name': '',
339                 f'Sample{form}.StorageName': '',
340                 f'Sample{form}.Order': '',
341             })
342
343         data.update(book_data)
344         data.update(files_data)
345
346         response = self.session.post(self.CREATE_URL, data=data)
347         m = re.search(r'/(\d+)$', response.url)
348         if m is not None:
349             return m.group(1)
350
351     def edit(self, legimi_id, data):
352         current = {
353             'ValidationTrue': 'true',
354             'Is': legimi_id
355         }
356
357         current.update(data)
358         
359         self.session.post(
360             self.EDIT_URL % legimi_id,
361             data=current
362         )
363
364     def edit_files(self, legimi_id, files_data):
365         current = {
366             'ValidationTrue': 'true',
367             'Id': legimi_id,
368             'SamplesGenerationType': 'Quantity',
369             'SamplesGenerationPercent': '10',
370         }
371
372         for form in 'Epub', 'Mobi', 'Pdf':
373             current.update({
374                 f'Book{form}.Token': '',
375                 f'Book{form}.Name': '',
376                 f'Book{form}.StorageName': '',
377                 f'Book{form}.Order': '',
378
379                 f'Sample{form}.Type': 'Files',
380                 f'Sample{form}.Token': '',
381                 f'Sample{form}.Name': '',
382                 f'Sample{form}.StorageName': '',
383                 f'Sample{form}.Order': '',
384             })
385
386         current.update(files_data)
387  
388         response = self.session.post(
389             self.EDIT_FILES_URL % legimi_id,
390             data=current
391         )
392
393     def edit_sale(self, book):
394         assert book.legimi_id
395
396         words = book.wldocument().get_statistics()['total']['words_with_fn']
397
398         price = settings.LEGIMI_SMALL_PRICE
399         if words > settings.LEGIMI_SMALL_WORDS:
400             price = settings.LEGIMI_BIG_PRICE
401
402         abo = 'true' if words > settings.LEGIMI_BIG_WORDS else 'false'
403
404         data = {
405             'ValidationTrue': 'true',
406             'Id': book.legimi_id,
407             'SalesPromotionId': "0",
408             'IsLibraryPass': "False",
409             'OriginalEnterToTheMarketType': "No",
410             'OriginalHidingDate': "",
411             'OriginalEnterToTheMarketDate': "",
412             'EnterToTheMarketType': "Yes",
413             'EnterToTheMarketDate': "",
414             'HidingDate': "",
415             'SalesNoLimitOption': abo,
416             'SalesNoLimitKindle': abo,
417             'SalesInStoreEbookGrossValue': f'{price},00',
418             'SalesPromotion': "False",
419             'SalesPromotionGrossValue': "0,00",
420             'SalesPromotionDatesRange.DateStart': "",
421             'SalesPromotionDatesRange.DateEnd': "",
422         }
423
424         self.session.post(
425             self.EDIT_SALE_URL % book.legimi_id,
426             data=data
427         )
428
429
430 legimi = Legimi(
431     settings.LEGIMI_USERNAME,
432     settings.LEGIMI_PASSWORD,
433     settings.LEGIMI_PUBLISHER_ID,
434 )