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