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