Completed
Push — master ( a91e9e...290003 )
by De
25s
created

comics.py (28 issues)

1
#! /usr/bin/python3
2
# vim: set expandtab tabstop=4 shiftwidth=4 :
3
"""Module to retrieve webcomics"""
4
5
from comic_abstract import GenericComic, get_date_for_comic
6
import re
7
from datetime import date, timedelta
8
import datetime
9
from urlfunctions import get_soup_at_url, urljoin_wrapper,\
10
    convert_iri_to_plain_ascii_uri, load_json_at_url, urlopen_wrapper
11
import json
12
import locale
13
import urllib
14
15
DEFAULT_LOCAL = 'en_GB.UTF-8'
16
17
18
class Xkcd(GenericComic):
19
    """Class to retrieve Xkcd comics."""
20
    name = 'xkcd'
21
    long_name = 'xkcd'
22
    url = 'http://xkcd.com'
23
24
    @classmethod
25
    def get_next_comic(cls, last_comic):
26
        """Generator to get the next comic. Implementation of GenericComic's abstract method."""
27
        json_url = urljoin_wrapper(cls.url, 'info.0.json')
28
        first_num = last_comic['num'] if last_comic else 0
29
        last_num = load_json_at_url(json_url)['num']
30
31
        for num in range(first_num + 1, last_num + 1):
32
            comic = cls.get_comic_info(num)
33
            if comic is not None:
34
                yield comic
35
36
    @classmethod
37
    def get_comic_info(cls, num):
38
        """Get information about a particular comics."""
39
        if num == 404:
40
            return None
41
        json_url = urljoin_wrapper(cls.url, '%d/info.0.json' % num)
42
        comic_json = load_json_at_url(json_url)
43
        assert comic_json['num'] == num, json_url
44
        return {
45
            'json_url': json_url,
46
            'num': num,
47
            'url': urljoin_wrapper(cls.url, str(num)),
48
            'prefix': '%d-' % num,
49
            'img': [comic_json['img']],
50
            'day': int(comic_json['day']),
51
            'month': int(comic_json['month']),
52
            'year': int(comic_json['year']),
53
            'link': comic_json['link'],
54
            'news': comic_json['news'],
55
            'safe_title': comic_json['safe_title'],
56
            'transcript': comic_json['transcript'],
57
            'alt': comic_json['alt'],
58
            'title': comic_json['title'],
59
        }
60
61
62
# Helper functions corresponding to get_url_from_link/get_url_from_archive_element
63
64
65
@classmethod
66
def get_href(cls, link):
67
    """Implementation of get_url_from_link/get_url_from_archive_element."""
68
    return link['href']
69
70
71
@classmethod
72
def join_cls_url_to_href(cls, link):
73
    """Implementation of get_url_from_link/get_url_from_archive_element."""
74
    return urljoin_wrapper(cls.url, link['href'])
75
76
77
class GenericNavigableComic(GenericComic):
78
    """Generic class for "navigable" comics : with first/next arrows.
79
80
    This class applies to comic where previous and next comics can be
81
    accessed from a given comic. Once given a starting point (either
82
    the first comic or the last comic retrieved), it will handle the
83
    navigation, the retrieval of the soup object and the setting of
84
    the 'url' attribute on retrieved comics. This limits a lot the
85
    amount of boilerplate code in the different implementation classes.
86
87
    The method `get_next_comic` methods is implemented in terms of new
88
    more specialized methods to be implemented/overridden:
89
        - get_first_comic_link
90
        - get_navi_link
91
        - get_comic_info
92
        - get_url_from_link
93
    """
94
    _categories = ('NAVIGABLE', )
95
96
    @classmethod
97
    def get_first_comic_link(cls):
98
        """Get link to first comics.
99
100
        Sometimes this can be retrieved of any comic page, sometimes on
101
        the archive page, sometimes it doesn't exist at all and one has
102
        to iterate backward to find it before hardcoding the result found.
103
        """
104
        raise NotImplementedError
105
106
    @classmethod
107
    def get_navi_link(cls, last_soup, next_):
108
        """Get link to next (or previous - for dev purposes) comic."""
109
        raise NotImplementedError
110
111
    @classmethod
112
    def get_comic_info(cls, soup, link):
113
        """Get information about a particular comics."""
114
        raise NotImplementedError
115
116
    @classmethod
117
    def get_url_from_link(cls, link):
118
        """Get url corresponding to a link. Default implementation is similar to get_href."""
119
        return link['href']
120
121
    @classmethod
122
    def get_next_link(cls, last_soup):
123
        """Get link to next comic."""
124
        link = cls.get_navi_link(last_soup, True)
125
        cls.log("Next link is %s" % link)
126
        return link
127
128
    @classmethod
129
    def get_prev_link(cls, last_soup):
130
        """Get link to previous comic."""
131
        link = cls.get_navi_link(last_soup, False)
132
        cls.log("Prev link is %s" % link)
133
        return link
134
135
    @classmethod
136
    def get_next_comic(cls, last_comic):
137
        """Generic implementation of get_next_comic for navigable comics."""
138
        url = last_comic['url'] if last_comic else None
139
        cls.log("starting 'get_next_comic' from %s" % url)
140
        next_comic = \
141
            cls.get_next_link(get_soup_at_url(url)) \
142
            if url else \
143
            cls.get_first_comic_link()
144
        cls.log("next/first comic will be %s (url is %s)" % (str(next_comic), url))
145
        # cls.check_navigation(url)
146
        while next_comic:
147
            prev_url, url = url, cls.get_url_from_link(next_comic)
148
            if prev_url == url:
149
                cls.log("got same url %s" % url)
150
                break
151
            cls.log("about to get %s (%s)" % (url, str(next_comic)))
152
            soup = get_soup_at_url(url)
153
            comic = cls.get_comic_info(soup, next_comic)
154
            if comic is not None:
155
                assert 'url' not in comic
156
                comic['url'] = url
157
                yield comic
158
            next_comic = cls.get_next_link(soup)
159
            cls.log("next comic will be %s" % str(next_comic))
160
161
    @classmethod
162
    def check_first_link(cls):
163
        """Check that navigation to first comic seems to be working - for dev purposes."""
164
        cls.log("about to check first link")
165
        ok = True
166
        firstlink = cls.get_first_comic_link()
167
        if firstlink is None:
168
            print("From %s : no first link" % cls.url)
169
            ok = False
170
        else:
171
            firsturl = cls.get_url_from_link(firstlink)
172
            try:
173
                get_soup_at_url(firsturl)
174
            except urllib.error.HTTPError:
175
                print("From %s : invalid first url" % cls.url)
176
                ok = False
177
        cls.log("checked first link -> returned %d" % ok)
178
        return ok
179
180
    @classmethod
181
    def check_prev_next_links(cls, url):
182
        """Check that navigation to prev/next from a given URL seems to be working - for dev purposes."""
183
        cls.log("about to check prev/next from %s" % url)
184
        ok = True
185
        if url is None:
186
            prevlink, nextlink = None, None
187
        else:
188
            soup = get_soup_at_url(url)
189
            prevlink, nextlink = cls.get_prev_link(soup), cls.get_next_link(soup)
190
        if prevlink is None and nextlink is None:
191
            print("From %s : no previous nor next" % url)
192
            ok = False
193
        else:
194
            if prevlink:
195
                prevurl = cls.get_url_from_link(prevlink)
196
                prevsoup = get_soup_at_url(prevurl)
197
                prevnextlink = cls.get_next_link(prevsoup)
198
                prevnext = cls.get_url_from_link(prevnextlink) if prevnextlink is not None else "NO URL"
199
                if prevnext != url:
200
                    print("From %s, going backward then forward leads to %s" % (url, prevnext))
201
                    ok = False
202
            if nextlink:
203
                nexturl = cls.get_url_from_link(nextlink)
204
                if nexturl != url:
205
                    nextsoup = get_soup_at_url(nexturl)
206
                    nextprevlink = cls.get_prev_link(nextsoup)
207
                    nextprev = cls.get_url_from_link(nextprevlink) if nextprevlink is not None else "NO URL"
208
                    if nextprev != url:
209
                        print("From %s, going forward then backward leads to %s" % (url, nextprev))
210
                        ok = False
211
        cls.log("checked prev/next from %s -> returned %d" % (url, ok))
212
        return ok
213
214
    @classmethod
215
    def check_navigation(cls, url):
216
        """Check that navigation functions seem to be working - for dev purposes."""
217
        cls.log("about to check navigation from %s" % url)
218
        first = cls.check_first_link()
219
        prevnext = cls.check_prev_next_links(url)
220
        ok = first and prevnext
221
        cls.log("checked navigation from %s -> returned %d" % (url, ok))
222
        return ok
223
224
225
class GenericListableComic(GenericComic):
226
    """Generic class for "listable" comics : with a list of comics (aka 'archive')
227
228
    The method `get_next_comic` methods is implemented in terms of new
229
    more specialized methods to be implemented/overridden:
230
        - get_archive_elements
231
        - get_url_from_archive_element
232
        - get_comic_info
233
    """
234
    _categories = ('LISTABLE', )
235
236
    @classmethod
237
    def get_archive_elements(cls):
238
        """Get the archive elements (iterable)."""
239
        raise NotImplementedError
240
241
    @classmethod
242
    def get_url_from_archive_element(cls, archive_elt):
243
        """Get url corresponding to an archive element."""
244
        raise NotImplementedError
245
246
    @classmethod
247
    def get_comic_info(cls, soup, archive_elt):
248
        """Get information about a particular comics."""
249
        raise NotImplementedError
250
251
    @classmethod
252
    def get_next_comic(cls, last_comic):
253
        """Generic implementation of get_next_comic for listable comics."""
254
        waiting_for_url = last_comic['url'] if last_comic else None
255
        archive_elts = list(cls.get_archive_elements())
256
        for archive_elt in archive_elts:
257
            url = cls.get_url_from_archive_element(archive_elt)
258
            cls.log("considering %s" % url)
259
            if waiting_for_url is None:
260
                cls.log("about to get %s (%s)" % (url, str(archive_elt)))
261
                soup = get_soup_at_url(url)
262
                comic = cls.get_comic_info(soup, archive_elt)
263
                if comic is not None:
264
                    assert 'url' not in comic
265
                    comic['url'] = url
266
                    yield comic
267
            elif waiting_for_url == url:
268
                waiting_for_url = None
269
        if waiting_for_url is not None:
270
            print("Did not find %s in the %d comics: there might be a problem" %
271
                  (waiting_for_url, len(archive_elts)))
272
273
# Helper functions corresponding to get_first_comic_link/get_navi_link
274
275
276
@classmethod
277
def get_link_rel_next(cls, last_soup, next_):
278
    """Implementation of get_navi_link."""
279
    return last_soup.find('link', rel='next' if next_ else 'prev')
280
281
282
@classmethod
283
def get_a_rel_next(cls, last_soup, next_):
284
    """Implementation of get_navi_link."""
285
    return last_soup.find('a', rel='next' if next_ else 'prev')
286
287
288
@classmethod
289
def get_a_navi_navinext(cls, last_soup, next_):
290
    """Implementation of get_navi_link."""
291
    # ComicPress (WordPress plugin)
292
    return last_soup.find('a', class_='navi navi-next' if next_ else 'navi navi-prev')
293
294
295
@classmethod
296
def get_a_navi_comicnavnext_navinext(cls, last_soup, next_):
297
    """Implementation of get_navi_link."""
298
    return last_soup.find('a', class_='navi comic-nav-next navi-next' if next_ else 'navi comic-nav-previous navi-prev')
299
300
301
@classmethod
302
def get_a_comicnavbase_comicnavnext(cls, last_soup, next_):
303
    """Implementation of get_navi_link."""
304
    return last_soup.find('a', class_='comic-nav-base comic-nav-next' if next_ else 'comic-nav-base comic-nav-previous')
305
306
307
@classmethod
308
def get_a_navi_navifirst(cls):
309
    """Implementation of get_first_comic_link."""
310
    # ComicPress (WordPress plugin)
311
    return get_soup_at_url(cls.url).find('a', class_='navi navi-first')
312
313
314
@classmethod
315
def get_div_navfirst_a(cls):
316
    """Implementation of get_first_comic_link."""
317
    return get_soup_at_url(cls.url).find('div', class_="nav-first").find('a')
318
319
320
@classmethod
321
def get_a_comicnavbase_comicnavfirst(cls):
322
    """Implementation of get_first_comic_link."""
323
    return get_soup_at_url(cls.url).find('a', class_='comic-nav-base comic-nav-first')
324
325
326
@classmethod
327
def simulate_first_link(cls):
328
    """Implementation of get_first_comic_link creating a link-like object from
329
    an URL provided by the class.
330
331
    Note: The first URL can easily be found using :
332
    `get_first_comic_link = navigate_to_first_comic`.
333
    """
334
    return {'href': cls.first_url}
335
336
337
@classmethod
338
def navigate_to_first_comic(cls):
339
    """Implementation of get_first_comic_link navigating from a user provided
340
    URL to the first comic.
341
342
    Sometimes, the first comic cannot be reached directly so to start
343
    from the first comic one has to go to the previous comic until
344
    there is no previous comics. Once this URL is reached, it
345
    is better to hardcode it but for development purposes, it
346
    is convenient to have an automatic way to find it.
347
348
    Then, the URL found can easily be used via `simulate_first_link`.
349
    """
350
    try:
351
        url = cls.first_url
352
    except AttributeError:
353
        url = input("Get starting URL: ")
354
    print(url)
355
    comic = cls.get_prev_link(get_soup_at_url(url))
356
    while comic:
357
        url = cls.get_url_from_link(comic)
358
        print(url)
359
        comic = cls.get_prev_link(get_soup_at_url(url))
360
    return {'href': url}
361
362
363
class GenericEmptyComic(GenericComic):
364
    """Generic class for comics where nothing is to be done.
365
366
    It can be useful to deactivate temporarily comics that do not work
367
    properly by replacing `def MyComic(GenericWhateverComic)` with
368
    `def MyComic(GenericEmptyComic, GenericWhateverComic)`."""
369
    _categories = ('EMPTY', )
370
371
    @classmethod
372
    def get_next_comic(cls, last_comic):
373
        """Implementation of get_next_comic returning no comics."""
374
        cls.log("comic is considered as empty - returning no comic")
375
        return []
376
377
378
class GenericComicNotWorking(GenericEmptyComic):
379
    """Subclass of GenericEmptyComic used when comic is not working.
380
381
    This is more explicit than GenericEmptyComic as it hilights that
382
    only the implementation is not working and it can be fixed."""
383
    _categories = ('NOTWORKING', )
384
385
386
class GenericUnavailableComic(GenericEmptyComic):
387
    """Subclass of GenericEmptyComic used when a comic is not available.
388
389
    This is more explicit than GenericEmptyComic as it hilights that
390
    the source of the comic is not available but we expect it to be back
391
    soonish. See also GenericDeletedComic."""
392
    _categories = ('UNAVAILABLE', )
393
394
395
class GenericDeletedComic(GenericEmptyComic):
396
    """Subclass of GenericEmptyComic used when a comic does not exist anymore.
397
398
    This is more explicit than GenericEmptyComic as it hilights that
399
    the source of the comic does not exist anymore and it probably cannot
400
    be fixed. Corresponding classes are kept as we can still use the
401
    downloaded data. See also GenericUnavailableComic."""
402
    _categories = ('DELETED', )
403
404
405 View Code Duplication
class ExtraFabulousComics(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
406
    """Class to retrieve Extra Fabulous Comics."""
407
    # Also on https://extrafabulouscomics.tumblr.com
408
    name = 'efc'
409
    long_name = 'Extra Fabulous Comics'
410
    url = 'http://extrafabulouscomics.com'
411
    _categories = ('EFC', )
412
    get_navi_link = get_link_rel_next
413
    get_first_comic_link = simulate_first_link
414
    first_url = 'http://extrafabulouscomics.com/comic/buttfly/'
415
416
    @classmethod
417
    def get_comic_info(cls, soup, link):
418
        """Get information about a particular comics."""
419
        img_src_re = re.compile('^%s/wp-content/uploads/' % cls.url)
420
        imgs = soup.find_all('img', src=img_src_re)
421
        title = soup.find('meta', property='og:title')['content']
422
        date_str = soup.find('meta', property='article:published_time')['content'][:10]
423
        day = string_to_date(date_str, "%Y-%m-%d")
424
        return {
425
            'title': title,
426
            'img': [i['src'] for i in imgs],
427
            'month': day.month,
428
            'year': day.year,
429
            'day': day.day,
430
            'prefix': title + '-'
431
        }
432
433
434 View Code Duplication
class GenericLeMondeBlog(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
435
    """Generic class to retrieve comics from Le Monde blogs."""
436
    _categories = ('LEMONDE', 'FRANCAIS')
437
    get_navi_link = get_link_rel_next
438
    get_first_comic_link = simulate_first_link
439
    first_url = NotImplemented
440
441
    @classmethod
442
    def get_comic_info(cls, soup, link):
443
        """Get information about a particular comics."""
444
        url2 = soup.find('link', rel='shortlink')['href']
445
        title = soup.find('meta', property='og:title')['content']
446
        date_str = soup.find("span", class_="entry-date").string
447
        day = string_to_date(date_str, "%d %B %Y", "fr_FR.utf8")
448
        imgs = soup.find_all('meta', property='og:image')
449
        return {
450
            'title': title,
451
            'url2': url2,
452
            'img': [convert_iri_to_plain_ascii_uri(i['content']) for i in imgs],
453
            'month': day.month,
454
            'year': day.year,
455
            'day': day.day,
456
        }
457
458
459
class ZepWorld(GenericLeMondeBlog):
460
    """Class to retrieve Zep World comics."""
461
    name = "zep"
462
    long_name = "Zep World"
463
    url = "http://zepworld.blog.lemonde.fr"
464
    first_url = "http://zepworld.blog.lemonde.fr/2014/10/31/bientot-le-blog-de-zep/"
465
466
467
class Vidberg(GenericLeMondeBlog):
468
    """Class to retrieve Vidberg comics."""
469
    name = 'vidberg'
470
    long_name = "Vidberg - l'actu en patates"
471
    url = "http://vidberg.blog.lemonde.fr"
472
    # Not the first but I didn't find an efficient way to retrieve it
473
    first_url = "http://vidberg.blog.lemonde.fr/2012/02/09/revue-de-campagne-la-campagne-du-modem-semballe/"
474
475
476
class Plantu(GenericLeMondeBlog):
477
    """Class to retrieve Plantu comics."""
478
    name = 'plantu'
479
    long_name = "Plantu"
480
    url = "http://plantu.blog.lemonde.fr"
481
    first_url = "http://plantu.blog.lemonde.fr/2014/10/28/stress-test-a-bruxelles/"
482
483
484
class XavierGorce(GenericLeMondeBlog):
485
    """Class to retrieve Xavier Gorce comics."""
486
    name = 'gorce'
487
    long_name = "Xavier Gorce"
488
    url = "http://xaviergorce.blog.lemonde.fr"
489
    first_url = "http://xaviergorce.blog.lemonde.fr/2015/01/09/distinction/"
490
491
492
class CartooningForPeace(GenericLeMondeBlog):
493
    """Class to retrieve Cartooning For Peace comics."""
494
    name = 'forpeace'
495
    long_name = "Cartooning For Peace"
496
    url = "http://cartooningforpeace.blog.lemonde.fr"
497
    first_url = "http://cartooningforpeace.blog.lemonde.fr/2014/12/15/bado/"
498
499
500
class Aurel(GenericLeMondeBlog):
501
    """Class to retrieve Aurel comics."""
502
    name = 'aurel'
503
    long_name = "Aurel"
504
    url = "http://aurel.blog.lemonde.fr"
505
    first_url = "http://aurel.blog.lemonde.fr/2014/09/29/le-senat-repasse-a-droite/"
506
507
508
class LesCulottees(GenericLeMondeBlog):
509
    """Class to retrieve Les Culottees comics."""
510
    name = 'culottees'
511
    long_name = 'Les Culottees'
512
    url = "http://lesculottees.blog.lemonde.fr"
513
    first_url = "http://lesculottees.blog.lemonde.fr/2016/01/11/clementine-delait-femme-a-barbe/"
514
515
516
class UneAnneeAuLycee(GenericLeMondeBlog):
517
    """Class to retrieve Une Annee Au Lycee comics."""
518
    name = 'lycee'
519
    long_name = 'Une Annee au Lycee'
520
    url = 'http://uneanneeaulycee.blog.lemonde.fr'
521
    first_url = "http://uneanneeaulycee.blog.lemonde.fr/2016/06/13/la-semaine-du-bac-est-arrivee/"
522
523
524 View Code Duplication
class Rall(GenericComicNotWorking, GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
525
    """Class to retrieve Ted Rall comics."""
526
    # Also on http://www.gocomics.com/tedrall
527
    name = 'rall'
528
    long_name = "Ted Rall"
529
    url = "http://rall.com/comic"
530
    _categories = ('RALL', )
531
    get_navi_link = get_link_rel_next
532
    get_first_comic_link = simulate_first_link
533
    # Not the first but I didn't find an efficient way to retrieve it
534
    first_url = "http://rall.com/2014/01/30/los-angeles-times-cartoon-well-miss-those-california-flowers"
535
536
    @classmethod
537
    def get_comic_info(cls, soup, link):
538
        """Get information about a particular comics."""
539
        title = soup.find('meta', property='og:title')['content']
540
        author = soup.find("span", class_="author vcard").find("a").string
541
        date_str = soup.find("span", class_="entry-date").string
542
        day = string_to_date(date_str, "%B %d, %Y")
543
        desc = soup.find('meta', property='og:description')['content']
544
        imgs = soup.find('div', class_='entry-content').find_all('img')
545
        imgs = imgs[:-7]  # remove social media buttons
546
        return {
547
            'title': title,
548
            'author': author,
549
            'month': day.month,
550
            'year': day.year,
551
            'day': day.day,
552
            'description': desc,
553
            'img': [i['src'] for i in imgs],
554
        }
555
556
557
class Dilem(GenericNavigableComic):
558
    """Class to retrieve Ali Dilem comics."""
559
    name = 'dilem'
560
    long_name = 'Ali Dilem'
561
    url = 'http://information.tv5monde.com/dilem'
562
    _categories = ('FRANCAIS', )
563
    get_url_from_link = join_cls_url_to_href
564
    get_first_comic_link = simulate_first_link
565
    first_url = "http://information.tv5monde.com/dilem/2004-06-26"
566
567
    @classmethod
568
    def get_navi_link(cls, last_soup, next_):
569
        """Get link to next or previous comic."""
570
        # prev is next / next is prev
571
        li = last_soup.find('li', class_='prev' if next_ else 'next')
572
        return li.find('a') if li else None
573
574
    @classmethod
575
    def get_comic_info(cls, soup, link):
576
        """Get information about a particular comics."""
577
        short_url = soup.find('link', rel='shortlink')['href']
578
        title = soup.find('meta', attrs={'name': 'twitter:title'})['content']
579
        imgs = soup.find_all('meta', property='og:image')
580
        date_str = soup.find('span', property='dc:date')['content']
581
        date_str = date_str[:10]
582
        day = string_to_date(date_str, "%Y-%m-%d")
583
        return {
584
            'short_url': short_url,
585
            'title': title,
586
            'img': [i['content'] for i in imgs],
587
            'day': day.day,
588
            'month': day.month,
589
            'year': day.year,
590
        }
591
592
593
class SpaceAvalanche(GenericNavigableComic):
594
    """Class to retrieve Space Avalanche comics."""
595
    name = 'avalanche'
596
    long_name = 'Space Avalanche'
597
    url = 'http://www.spaceavalanche.com'
598
    get_navi_link = get_link_rel_next
599
600
    @classmethod
601
    def get_first_comic_link(cls):
602
        """Get link to first comics."""
603
        return {'href': "http://www.spaceavalanche.com/2009/02/02/irish-sea/", 'title': "Irish Sea"}
604
605
    @classmethod
606
    def get_comic_info(cls, soup, link):
607
        """Get information about a particular comics."""
608
        url_date_re = re.compile('.*/([0-9]*)/([0-9]*)/([0-9]*)/.*$')
609
        title = link['title']
610
        url = cls.get_url_from_link(link)
611
        year, month, day = [int(s)
612
                            for s in url_date_re.match(url).groups()]
613
        imgs = soup.find("div", class_="entry").find_all("img")
614
        return {
615
            'title': title,
616
            'day': day,
617
            'month': month,
618
            'year': year,
619
            'img': [i['src'] for i in imgs],
620
        }
621
622
623
class ZenPencils(GenericNavigableComic):
624
    """Class to retrieve ZenPencils comics."""
625
    # Also on http://zenpencils.tumblr.com
626
    # Also on http://www.gocomics.com/zen-pencils
627
    name = 'zenpencils'
628
    long_name = 'Zen Pencils'
629
    url = 'http://zenpencils.com'
630
    _categories = ('ZENPENCILS', )
631
    get_navi_link = get_link_rel_next
632
    get_first_comic_link = simulate_first_link
633
    first_url = "http://zenpencils.com/comic/1-ralph-waldo-emerson-make-them-cry/"
634
635
    @classmethod
636
    def get_comic_info(cls, soup, link):
637
        """Get information about a particular comics."""
638
        imgs = soup.find('div', id='comic').find_all('img')
639
        # imgs2 = soup.find_all('meta', property='og:image')
640
        post = soup.find('div', class_='post-content')
641
        author = post.find("span", class_="post-author").find("a").string
642
        title = soup.find('h2', class_='post-title').string
643
        date_str = post.find('span', class_='post-date').string
644
        day = string_to_date(date_str, "%B %d, %Y")
645
        assert imgs
646
        assert all(i['alt'] == i['title'] for i in imgs)
647
        assert all(i['alt'] in (title, "") for i in imgs)
648
        return {
649
            'title': title,
650
            'author': author,
651
            'day': day.day,
652
            'month': day.month,
653
            'year': day.year,
654
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
655
        }
656
657
658
class ItsTheTie(GenericDeletedComic, GenericNavigableComic):
659
    """Class to retrieve It's the tie comics."""
660
    # Also on http://itsthetie.tumblr.com
661
    # Also on https://tapastic.com/series/itsthetie
662
    name = 'tie'
663
    long_name = "It's the tie"
664
    url = "http://itsthetie.com"
665
    _categories = ('TIE', )
666
    get_first_comic_link = get_div_navfirst_a
667
    get_navi_link = get_a_rel_next
668
669
    @classmethod
670
    def get_comic_info(cls, soup, link):
671
        """Get information about a particular comics."""
672
        title = soup.find('h1', class_='comic-title').find('a').string
673
        date_str = soup.find('header', class_='comic-meta entry-meta').find('a').string
674
        day = string_to_date(date_str, "%B %d, %Y")
675
        # Bonus images may or may not be in meta og:image.
676
        imgs = soup.find_all('meta', property='og:image')
677
        imgs_src = [i['content'] for i in imgs]
678
        bonus = soup.find_all('img', attrs={'data-oversrc': True})
679
        bonus_src = [b['data-oversrc'] for b in bonus]
680
        all_imgs_src = imgs_src + [s for s in bonus_src if s not in imgs_src]
681
        all_imgs_src = [s for s in all_imgs_src if not s.endswith("/2016/01/bonus-panel.png")]
682
        tag_meta = soup.find('meta', property='article:tag')
683
        tags = tag_meta['content'] if tag_meta else ""
684
        return {
685
            'title': title,
686
            'month': day.month,
687
            'year': day.year,
688
            'day': day.day,
689
            'img': all_imgs_src,
690
            'tags': tags,
691
        }
692
693
694
class PenelopeBagieu(GenericNavigableComic):
695
    """Class to retrieve comics from Penelope Bagieu's blog."""
696
    name = 'bagieu'
697
    long_name = 'Ma vie est tout a fait fascinante (Bagieu)'
698
    url = 'http://www.penelope-jolicoeur.com'
699
    _categories = ('FRANCAIS', )
700
    get_navi_link = get_link_rel_next
701
    get_first_comic_link = simulate_first_link
702
    first_url = 'http://www.penelope-jolicoeur.com/2007/02/ma-vie-mon-oeuv.html'
703
704
    @classmethod
705
    def get_comic_info(cls, soup, link):
706
        """Get information about a particular comics."""
707
        date_str = soup.find('h2', class_='date-header').string
708
        day = string_to_date(date_str, "%A %d %B %Y", "fr_FR.utf8")
709
        imgs = soup.find('div', class_='entry-body').find_all('img')
710
        title = soup.find('h3', class_='entry-header').string
711
        return {
712
            'title': title,
713
            'img': [i['src'] for i in imgs],
714
            'month': day.month,
715
            'year': day.year,
716
            'day': day.day,
717
        }
718
719
720 View Code Duplication
class OneOneOneOneComic(GenericComicNotWorking, GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
721
    """Class to retrieve 1111 Comics."""
722
    # Also on http://comics1111.tumblr.com
723
    # Also on https://tapastic.com/series/1111-Comics
724
    name = '1111'
725
    long_name = '1111 Comics'
726
    url = 'http://www.1111comics.me'
727
    _categories = ('ONEONEONEONE', )
728
    get_first_comic_link = get_div_navfirst_a
729
    get_navi_link = get_link_rel_next
730
731
    @classmethod
732
    def get_comic_info(cls, soup, link):
733
        """Get information about a particular comics."""
734
        title = soup.find('h1', class_='comic-title').find('a').string
735
        date_str = soup.find('header', class_='comic-meta entry-meta').find('a').string
736
        day = string_to_date(date_str, "%B %d, %Y")
737
        imgs = soup.find_all('meta', property='og:image')
738
        return {
739
            'title': title,
740
            'month': day.month,
741
            'year': day.year,
742
            'day': day.day,
743
            'img': [i['content'] for i in imgs],
744
        }
745
746
747 View Code Duplication
class AngryAtNothing(GenericDeletedComic, GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
748
    """Class to retrieve Angry at Nothing comics."""
749
    # Also on http://tapastic.com/series/Comics-yeah-definitely-comics-
750
    # Also on http://angryatnothing.tumblr.com
751
    name = 'angry'
752
    long_name = 'Angry At Nothing'
753
    url = 'http://www.angryatnothing.net'
754
    get_first_comic_link = get_div_navfirst_a
755
    get_navi_link = get_a_rel_next
756
757
    @classmethod
758
    def get_comic_info(cls, soup, link):
759
        """Get information about a particular comics."""
760
        title = soup.find('h1', class_='comic-title').find('a').string
761
        date_str = soup.find('header', class_='comic-meta entry-meta').find('a').string
762
        day = string_to_date(date_str, "%B %d, %Y")
763
        imgs = soup.find_all('meta', property='og:image')
764
        return {
765
            'title': title,
766
            'month': day.month,
767
            'year': day.year,
768
            'day': day.day,
769
            'img': [i['content'] for i in imgs],
770
        }
771
772
773
class NeDroid(GenericNavigableComic):
774
    """Class to retrieve NeDroid comics."""
775
    name = 'nedroid'
776
    long_name = 'NeDroid'
777
    url = 'http://nedroid.com'
778
    get_first_comic_link = get_div_navfirst_a
779
    get_navi_link = get_link_rel_next
780
    get_url_from_link = join_cls_url_to_href
781
782 View Code Duplication
    @classmethod
783
    def get_comic_info(cls, soup, link):
784
        """Get information about a particular comics."""
785
        short_url_re = re.compile('^%s/\\?p=([0-9]*)' % cls.url)
786
        short_url = cls.get_url_from_link(soup.find('link', rel='shortlink'))
787
        num = int(short_url_re.match(short_url).groups()[0])
788
        imgs = soup.find('div', id='comic').find_all('img')
789
        assert len(imgs) == 1, imgs
790
        title = imgs[0]['alt']
791
        title2 = imgs[0]['title']
792
        return {
793
            'short_url': short_url,
794
            'title': title,
795
            'title2': title2,
796
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
797
            'num': num,
798
        }
799
800
801
class Garfield(GenericNavigableComic):
802
    """Class to retrieve Garfield comics."""
803
    # Also on http://www.gocomics.com/garfield
804
    name = 'garfield'
805
    long_name = 'Garfield'
806
    url = 'https://garfield.com'
807
    _categories = ('GARFIELD', )
808
    get_first_comic_link = simulate_first_link
809
    first_url = 'https://garfield.com/comic/1978/06/19'
810
811
    @classmethod
812
    def get_navi_link(cls, last_soup, next_):
813
        """Get link to next or previous comic."""
814
        return last_soup.find('a', class_='comic-arrow-right' if next_ else 'comic-arrow-left')
815
816
    @classmethod
817
    def get_comic_info(cls, soup, link):
818
        """Get information about a particular comics."""
819
        url = cls.get_url_from_link(link)
820
        date_re = re.compile('^%s/comic/([0-9]*)/([0-9]*)/([0-9]*)' % cls.url)
821
        year, month, day = [int(s) for s in date_re.match(url).groups()]
822
        imgs = soup.find('div', class_='comic-display').find_all('img', class_='img-responsive')
823
        return {
824
            'month': month,
825
            'year': year,
826
            'day': day,
827
            'img': [i['src'] for i in imgs],
828
        }
829
830
831
class Dilbert(GenericNavigableComic):
832
    """Class to retrieve Dilbert comics."""
833
    # Also on http://www.gocomics.com/dilbert-classics
834
    name = 'dilbert'
835
    long_name = 'Dilbert'
836
    url = 'http://dilbert.com'
837
    get_url_from_link = join_cls_url_to_href
838
    get_first_comic_link = simulate_first_link
839
    first_url = 'http://dilbert.com/strip/1989-04-16'
840
841
    @classmethod
842
    def get_navi_link(cls, last_soup, next_):
843
        """Get link to next or previous comic."""
844
        link = last_soup.find('div', class_='nav-comic nav-right' if next_ else 'nav-comic nav-left')
845
        return link.find('a') if link else None
846
847 View Code Duplication
    @classmethod
848
    def get_comic_info(cls, soup, link):
849
        """Get information about a particular comics."""
850
        title = soup.find('meta', property='og:title')['content']
851
        imgs = soup.find_all('meta', property='og:image')
852
        desc = soup.find('meta', property='og:description')['content']
853
        date_str = soup.find('meta', property='article:publish_date')['content']
854
        day = string_to_date(date_str, "%B %d, %Y")
855
        author = soup.find('meta', property='article:author')['content']
856
        tags = soup.find('meta', property='article:tag')['content']
857
        return {
858
            'title': title,
859
            'description': desc,
860
            'img': [i['content'] for i in imgs],
861
            'author': author,
862
            'tags': tags,
863
            'day': day.day,
864
            'month': day.month,
865
            'year': day.year
866
        }
867
868
869
class VictimsOfCircumsolar(GenericDeletedComic, GenericNavigableComic):
870
    """Class to retrieve VictimsOfCircumsolar comics."""
871
    # Also on https://victimsofcomics.tumblr.com
872
    name = 'circumsolar'
873
    long_name = 'Victims Of Circumsolar'
874
    url = 'http://www.victimsofcircumsolar.com'
875
    get_navi_link = get_a_navi_comicnavnext_navinext
876
    get_first_comic_link = simulate_first_link
877
    first_url = 'http://www.victimsofcircumsolar.com/comic/modern-addiction'
878
879
    @classmethod
880
    def get_comic_info(cls, soup, link):
881
        """Get information about a particular comics."""
882
        # Date is on the archive page
883
        title = soup.find_all('meta', property='og:title')[-1]['content']
884
        desc = soup.find_all('meta', property='og:description')[-1]['content']
885
        imgs = soup.find('div', id='comic').find_all('img')
886
        assert all(i['title'] == i['alt'] == title for i in imgs)
887
        return {
888
            'title': title,
889
            'description': desc,
890
            'img': [i['src'] for i in imgs],
891
        }
892
893
894
class ThreeWordPhrase(GenericNavigableComic):
895
    """Class to retrieve Three Word Phrase comics."""
896
    # Also on http://www.threewordphrase.tumblr.com
897
    name = 'threeword'
898
    long_name = 'Three Word Phrase'
899
    url = 'http://threewordphrase.com'
900
    get_url_from_link = join_cls_url_to_href
901
902
    @classmethod
903
    def get_first_comic_link(cls):
904
        """Get link to first comics."""
905
        return get_soup_at_url(cls.url).find('img', src='/firstlink.gif').parent
906
907
    @classmethod
908
    def get_navi_link(cls, last_soup, next_):
909
        """Get link to next or previous comic."""
910
        link = last_soup.find('img', src='/nextlink.gif' if next_ else '/prevlink.gif').parent
911
        return None if link.get('href') is None else link
912
913
    @classmethod
914
    def get_comic_info(cls, soup, link):
915
        """Get information about a particular comics."""
916
        title = soup.find('title')
917
        imgs = [img for img in soup.find_all('img')
918
                if not img['src'].endswith(
919
                    ('link.gif', '32.png', 'twpbookad.jpg',
920
                     'merchad.jpg', 'header.gif', 'tipjar.jpg'))]
921
        return {
922
            'title': title.string if title else None,
923
            'title2': '  '.join(img.get('alt') for img in imgs if img.get('alt')),
924
            'img': [urljoin_wrapper(cls.url, img['src']) for img in imgs],
925
        }
926
927
928
class DeadlyPanel(GenericComicNotWorking, GenericNavigableComic):  # Not working on my machine
929
    """Class to retrieve Deadly Panel comics."""
930
    # Also on https://tapastic.com/series/deadlypanel
931
    # Also on https://deadlypanel.tumblr.com
932
    name = 'deadly'
933
    long_name = 'Deadly Panel'
934
    url = 'http://www.deadlypanel.com'
935
    get_first_comic_link = get_a_navi_navifirst
936
    get_navi_link = get_a_navi_comicnavnext_navinext
937
938
    @classmethod
939
    def get_comic_info(cls, soup, link):
940
        """Get information about a particular comics."""
941
        imgs = soup.find('div', id='comic').find_all('img')
942
        assert all(i['alt'] == i['title'] for i in imgs)
943
        return {
944
            'img': [i['src'] for i in imgs],
945
        }
946
947
948 View Code Duplication
class TheGentlemanArmchair(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
949
    """Class to retrieve The Gentleman Armchair comics."""
950
    name = 'gentlemanarmchair'
951
    long_name = 'The Gentleman Armchair'
952
    url = 'http://thegentlemansarmchair.com'
953
    get_first_comic_link = get_a_navi_navifirst
954
    get_navi_link = get_link_rel_next
955
956
    @classmethod
957
    def get_comic_info(cls, soup, link):
958
        """Get information about a particular comics."""
959
        title = soup.find('h2', class_='post-title').string
960
        author = soup.find("span", class_="post-author").find("a").string
961
        date_str = soup.find('span', class_='post-date').string
962
        day = string_to_date(date_str, "%B %d, %Y")
963
        imgs = soup.find('div', id='comic').find_all('img')
964
        return {
965
            'img': [i['src'] for i in imgs],
966
            'title': title,
967
            'author': author,
968
            'month': day.month,
969
            'year': day.year,
970
            'day': day.day,
971
        }
972
973
974 View Code Duplication
class ImogenQuest(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
975
    """Class to retrieve Imogen Quest comics."""
976
    # Also on http://imoquest.tumblr.com
977
    name = 'imogen'
978
    long_name = 'Imogen Quest'
979
    url = 'http://imogenquest.net'
980
    get_first_comic_link = get_div_navfirst_a
981
    get_navi_link = get_a_rel_next
982
983
    @classmethod
984
    def get_comic_info(cls, soup, link):
985
        """Get information about a particular comics."""
986
        title = soup.find('h2', class_='post-title').string
987
        author = soup.find("span", class_="post-author").find("a").string
988
        date_str = soup.find('span', class_='post-date').string
989
        day = string_to_date(date_str, '%B %d, %Y')
990
        imgs = soup.find('div', class_='comicpane').find_all('img')
991
        assert all(i['alt'] == i['title'] for i in imgs)
992
        title2 = imgs[0]['title']
993
        return {
994
            'day': day.day,
995
            'month': day.month,
996
            'year': day.year,
997
            'img': [i['src'] for i in imgs],
998
            'title': title,
999
            'title2': title2,
1000
            'author': author,
1001
        }
1002
1003
1004
class MyExtraLife(GenericNavigableComic):
1005
    """Class to retrieve My Extra Life comics."""
1006
    name = 'extralife'
1007
    long_name = 'My Extra Life'
1008
    url = 'http://www.myextralife.com'
1009
    get_navi_link = get_link_rel_next
1010
1011
    @classmethod
1012
    def get_first_comic_link(cls):
1013
        """Get link to first comics."""
1014
        return get_soup_at_url(cls.url).find('a', class_='comic_nav_link first_comic_link')
1015
1016
    @classmethod
1017
    def get_comic_info(cls, soup, link):
1018
        """Get information about a particular comics."""
1019
        title = soup.find("h1", class_="comic_title").string
1020
        date_str = soup.find("span", class_="comic_date").string
1021
        day = string_to_date(date_str, "%B %d, %Y")
1022
        imgs = soup.find_all("img", class_="comic")
1023
        assert all(i['alt'] == i['title'] == title for i in imgs)
1024
        return {
1025
            'title': title,
1026
            'img': [i['src'] for i in imgs if i["src"]],
1027
            'day': day.day,
1028
            'month': day.month,
1029
            'year': day.year
1030
        }
1031
1032
1033
class SaturdayMorningBreakfastCereal(GenericNavigableComic):
1034
    """Class to retrieve Saturday Morning Breakfast Cereal comics."""
1035
    # Also on http://www.gocomics.com/saturday-morning-breakfast-cereal
1036
    # Also on http://smbc-comics.tumblr.com
1037
    name = 'smbc'
1038
    long_name = 'Saturday Morning Breakfast Cereal'
1039
    url = 'http://www.smbc-comics.com'
1040
    _categories = ('SMBC', )
1041
    get_navi_link = get_a_rel_next
1042
1043
    @classmethod
1044
    def get_first_comic_link(cls):
1045
        """Get link to first comics."""
1046
        return get_soup_at_url(cls.url).find('a', rel='start')
1047
1048
    @classmethod
1049
    def get_comic_info(cls, soup, link):
1050
        """Get information about a particular comics."""
1051
        image1 = soup.find('img', id='cc-comic')
1052
        image_url1 = image1['src']
1053
        aftercomic = soup.find('div', id='aftercomic')
1054
        image_url2 = aftercomic.find('img')['src'] if aftercomic else ''
1055
        imgs = [image_url1] + ([image_url2] if image_url2 else [])
1056
        date_str = soup.find('div', class_='cc-publishtime').contents[0]
1057
        day = string_to_date(date_str, "%B %d, %Y")
1058
        return {
1059
            'title': image1['title'],
1060
            'img': [convert_iri_to_plain_ascii_uri(urljoin_wrapper(cls.url, i)) for i in imgs],
1061
            'day': day.day,
1062
            'month': day.month,
1063
            'year': day.year
1064
        }
1065
1066
1067 View Code Duplication
class PerryBibleFellowship(GenericListableComic):  # Is now navigable too
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
1068
    """Class to retrieve Perry Bible Fellowship comics."""
1069
    name = 'pbf'
1070
    long_name = 'Perry Bible Fellowship'
1071
    url = 'http://pbfcomics.com'
1072
    get_url_from_archive_element = join_cls_url_to_href
1073
1074
    @classmethod
1075
    def get_archive_elements(cls):
1076
        soup = get_soup_at_url(cls.url)
1077
        thumbnails = soup.find('div', id='all_thumbnails')
1078
        return reversed(thumbnails.find_all('a'))
1079
1080
    @classmethod
1081
    def get_comic_info(cls, soup, link):
1082
        """Get information about a particular comics."""
1083
        name = soup.find('meta', property='og:title')['content']
1084
        imgs = soup.find_all('meta', property='og:image')
1085
        assert len(imgs) == 1, imgs
1086
        return {
1087
            'name': name,
1088
            'img': [i['content'] for i in imgs],
1089
        }
1090
1091
1092 View Code Duplication
class Mercworks(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
1093
    """Class to retrieve Mercworks comics."""
1094
    # Also on http://mercworks.tumblr.com
1095
    name = 'mercworks'
1096
    long_name = 'Mercworks'
1097
    url = 'http://mercworks.net'
1098
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
1099
    get_navi_link = get_link_rel_next
1100
1101
    @classmethod
1102
    def get_comic_info(cls, soup, link):
1103
        """Get information about a particular comics."""
1104
        title = soup.find('meta', property='og:title')['content']
1105
        metadesc = soup.find('meta', property='og:description')
1106
        desc = metadesc['content'] if metadesc else ""
1107
        date_str = soup.find('meta', property='article:published_time')['content'][:10]
1108
        day = string_to_date(date_str, "%Y-%m-%d")
1109
        imgs = soup.find_all('meta', property='og:image')
1110
        return {
1111
            'img': [i['content'] for i in imgs],
1112
            'title': title,
1113
            'desc': desc,
1114
            'day': day.day,
1115
            'month': day.month,
1116
            'year': day.year
1117
        }
1118
1119
1120
class BerkeleyMews(GenericListableComic):
1121
    """Class to retrieve Berkeley Mews comics."""
1122
    # Also on http://mews.tumblr.com
1123
    # Also on http://www.gocomics.com/berkeley-mews
1124
    name = 'berkeley'
1125
    long_name = 'Berkeley Mews'
1126
    url = 'http://www.berkeleymews.com'
1127
    _categories = ('BERKELEY', )
1128
    get_url_from_archive_element = get_href
1129
    comic_num_re = re.compile('%s/\\?p=([0-9]*)$' % url)
1130
1131
    @classmethod
1132
    def get_archive_elements(cls):
1133
        archive_url = urljoin_wrapper(cls.url, "?page_id=2")
1134
        return reversed(get_soup_at_url(archive_url).find_all('a', href=cls.comic_num_re))
1135
1136
    @classmethod
1137
    def get_comic_info(cls, soup, link):
1138
        """Get information about a particular comics."""
1139
        comic_date_re = re.compile('.*/([0-9]*)-([0-9]*)-([0-9]*)-.*')
1140
        url = cls.get_url_from_archive_element(link)
1141
        num = int(cls.comic_num_re.match(url).groups()[0])
1142
        img = soup.find('div', id='comic').find('img')
1143
        assert all(i['alt'] == i['title'] for i in [img])
1144
        title2 = img['title']
1145
        img_url = img['src']
1146
        year, month, day = [int(s) for s in comic_date_re.match(img_url).groups()]
1147
        return {
1148
            'num': num,
1149
            'title': link.string,
1150
            'title2': title2,
1151
            'img': [img_url],
1152
            'year': year,
1153
            'month': month,
1154
            'day': day,
1155
        }
1156
1157
1158
class GenericBouletCorp(GenericNavigableComic):
1159
    """Generic class to retrieve BouletCorp comics in different languages."""
1160
    # Also on https://bouletcorp.tumblr.com
1161
    _categories = ('BOULET', )
1162
    get_navi_link = get_link_rel_next
1163
1164
    @classmethod
1165
    def get_first_comic_link(cls):
1166
        """Get link to first comics."""
1167
        return get_soup_at_url(cls.url).find('div', id='centered_nav').find_all('a')[0]
1168
1169
    @classmethod
1170
    def get_comic_info(cls, soup, link):
1171
        """Get information about a particular comics."""
1172
        url = cls.get_url_from_link(link)
1173
        date_re = re.compile('^%s/([0-9]*)/([0-9]*)/([0-9]*)/' % cls.url)
1174
        year, month, day = [int(s) for s in date_re.match(url).groups()]
1175
        imgs = soup.find('div', id='notes').find('div', class_='storycontent').find_all('img')
1176
        texts = '  '.join(t for t in (i.get('title') for i in imgs) if t)
1177
        title = soup.find('title').string
1178
        return {
1179
            'img': [convert_iri_to_plain_ascii_uri(i['src']) for i in imgs if i.get('src') is not None],
1180
            'title': title,
1181
            'texts': texts,
1182
            'year': year,
1183
            'month': month,
1184
            'day': day,
1185
        }
1186
1187
1188
class BouletCorp(GenericBouletCorp):
1189
    """Class to retrieve BouletCorp comics."""
1190
    name = 'boulet'
1191
    long_name = 'Boulet Corp'
1192
    url = 'http://www.bouletcorp.com'
1193
    _categories = ('FRANCAIS', )
1194
1195
1196
class BouletCorpEn(GenericBouletCorp):
1197
    """Class to retrieve EnglishBouletCorp comics."""
1198
    name = 'boulet_en'
1199
    long_name = 'Boulet Corp English'
1200
    url = 'http://english.bouletcorp.com'
1201
1202
1203 View Code Duplication
class AmazingSuperPowers(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
1204
    """Class to retrieve Amazing Super Powers comics."""
1205
    name = 'asp'
1206
    long_name = 'Amazing Super Powers'
1207
    url = 'http://www.amazingsuperpowers.com'
1208
    get_first_comic_link = get_a_navi_navifirst
1209
    get_navi_link = get_a_navi_navinext
1210
1211
    @classmethod
1212
    def get_comic_info(cls, soup, link):
1213
        """Get information about a particular comics."""
1214
        author = soup.find("span", class_="post-author").find("a").string
1215
        date_str = soup.find('span', class_='post-date').string
1216
        day = string_to_date(date_str, "%B %d, %Y")
1217
        imgs = soup.find('div', id='comic').find_all('img')
1218
        title = ' '.join(i['title'] for i in imgs)
1219
        assert all(i['alt'] == i['title'] for i in imgs)
1220
        return {
1221
            'title': title,
1222
            'author': author,
1223
            'img': [img['src'] for img in imgs],
1224
            'day': day.day,
1225
            'month': day.month,
1226
            'year': day.year
1227
        }
1228
1229
1230
class ToonHole(GenericNavigableComic):
1231
    """Class to retrieve Toon Holes comics."""
1232
    # Also on http://tapastic.com/series/TOONHOLE
1233
    name = 'toonhole'
1234
    long_name = 'Toon Hole'
1235
    url = 'http://www.toonhole.com'
1236
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
1237
    get_navi_link = get_a_comicnavbase_comicnavnext
1238
1239
    @classmethod
1240
    def get_comic_info(cls, soup, link):
1241
        """Get information about a particular comics."""
1242
        date_str = soup.find('div', class_='entry-meta').contents[0].strip()
1243
        day = string_to_date(date_str, "%B %d, %Y")
1244
        imgs = soup.find('div', id='comic').find_all('img')
1245
        if imgs:
1246
            img = imgs[0]
1247
            title = img['alt']
1248
            assert img['title'] == title
1249
        else:
1250
            title = ""
1251
        return {
1252
            'title': title,
1253
            'month': day.month,
1254
            'year': day.year,
1255
            'day': day.day,
1256
            'img': [convert_iri_to_plain_ascii_uri(i['src']) for i in imgs],
1257
        }
1258
1259
1260
class Channelate(GenericNavigableComic):
1261
    """Class to retrieve Channelate comics."""
1262
    name = 'channelate'
1263
    long_name = 'Channelate'
1264
    url = 'http://www.channelate.com'
1265
    get_first_comic_link = get_div_navfirst_a
1266
    get_navi_link = get_link_rel_next
1267
    get_url_from_link = join_cls_url_to_href
1268
1269
    @classmethod
1270
    def get_comic_info(cls, soup, link):
1271
        """Get information about a particular comics."""
1272
        author = soup.find("span", class_="post-author").find("a").string
1273
        date_str = soup.find('span', class_='post-date').string
1274
        day = string_to_date(date_str, '%Y/%m/%d')
1275
        title = soup.find('meta', property='og:title')['content']
1276
        post = soup.find('div', id='comic')
1277
        imgs = post.find_all('img') if post else []
1278
        extra_url = None
1279
        extra_div = soup.find('div', id='extrapanelbutton')
1280
        if extra_div:
1281
            extra_url = extra_div.find('a')['href']
1282
            extra_soup = get_soup_at_url(extra_url)
1283
            extra_imgs = extra_soup.find_all('img', class_='extrapanelimage')
1284
            imgs.extend(extra_imgs)
1285
        return {
1286
            'url_extra': extra_url,
1287
            'title': title,
1288
            'author': author,
1289
            'month': day.month,
1290
            'year': day.year,
1291
            'day': day.day,
1292
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
1293
        }
1294
1295
1296
class CyanideAndHappiness(GenericNavigableComic):
1297
    """Class to retrieve Cyanide And Happiness comics."""
1298
    name = 'cyanide'
1299
    long_name = 'Cyanide and Happiness'
1300
    url = 'http://explosm.net'
1301
    _categories = ('NSFW', )
1302
    get_url_from_link = join_cls_url_to_href
1303
1304
    @classmethod
1305
    def get_first_comic_link(cls):
1306
        """Get link to first comics."""
1307
        return get_soup_at_url(cls.url).find('a', title='Oldest comic')
1308
1309
    @classmethod
1310
    def get_navi_link(cls, last_soup, next_):
1311
        """Get link to next or previous comic."""
1312
        link = last_soup.find('a', class_='next-comic' if next_ else 'previous-comic ')
1313
        return None if link.get('href') is None else link
1314
1315
    @classmethod
1316
    def get_comic_info(cls, soup, link):
1317
        """Get information about a particular comics."""
1318
        url2 = soup.find('meta', property='og:url')['content']
1319
        num = int(url2.split('/')[-2])
1320
        date_str = soup.find('h3').find('a').string
1321
        day = string_to_date(date_str, '%Y.%m.%d')
1322
        author = soup.find('small', class_="author-credit-name").string
1323
        assert author.startswith('by ')
1324
        author = author[3:]
1325
        imgs = soup.find_all('img', id='main-comic')
1326
        return {
1327
            'num': num,
1328
            'author': author,
1329
            'month': day.month,
1330
            'year': day.year,
1331
            'day': day.day,
1332
            'prefix': '%d-' % num,
1333
            'img': [convert_iri_to_plain_ascii_uri(urljoin_wrapper(cls.url, i['src'])) for i in imgs]
1334
        }
1335
1336
1337
class MrLovenstein(GenericComic):
1338
    """Class to retrieve Mr Lovenstein comics."""
1339
    # Also on https://tapastic.com/series/MrLovenstein
1340
    name = 'mrlovenstein'
1341
    long_name = 'Mr. Lovenstein'
1342
    url = 'http://www.mrlovenstein.com'
1343
1344
    @classmethod
1345
    def get_next_comic(cls, last_comic):
1346
        """Generator to get the next comic. Implementation of GenericComic's abstract method."""
1347
        # TODO: more info from http://www.mrlovenstein.com/archive
1348
        comic_num_re = re.compile('^/comic/([0-9]*)$')
1349
        nums = [int(comic_num_re.match(link['href']).groups()[0])
1350
                for link in get_soup_at_url(cls.url).find_all('a', href=comic_num_re)]
1351
        first, last = min(nums), max(nums)
1352
        if last_comic:
1353
            first = last_comic['num'] + 1
1354
        for num in range(first, last + 1):
1355
            url = urljoin_wrapper(cls.url, '/comic/%d' % num)
1356
            soup = get_soup_at_url(url)
1357
            imgs = list(
1358
                reversed(soup.find_all('img', src=re.compile('^/images/comics/'))))
1359
            description = soup.find('meta', attrs={'name': 'description'})['content']
1360
            yield {
1361
                'url': url,
1362
                'num': num,
1363
                'texts': '  '.join(t for t in (i.get('title') for i in imgs) if t),
1364
                'img': [urljoin_wrapper(url, i['src']) for i in imgs],
1365
                'description': description,
1366
            }
1367
1368
1369
class DinosaurComics(GenericListableComic):
1370
    """Class to retrieve Dinosaur Comics comics."""
1371
    name = 'dinosaur'
1372
    long_name = 'Dinosaur Comics'
1373
    url = 'http://www.qwantz.com'
1374
    get_url_from_archive_element = get_href
1375
    comic_link_re = re.compile('^%s/index.php\\?comic=([0-9]*)$' % url)
1376
1377
    @classmethod
1378
    def get_archive_elements(cls):
1379
        archive_url = urljoin_wrapper(cls.url, 'archive.php')
1380
        # first link is random -> skip it
1381
        return reversed(get_soup_at_url(archive_url).find_all('a', href=cls.comic_link_re)[1:])
1382
1383
    @classmethod
1384
    def get_comic_info(cls, soup, link):
1385
        """Get information about a particular comics."""
1386 View Code Duplication
        url = cls.get_url_from_archive_element(link)
1387
        num = int(cls.comic_link_re.match(url).groups()[0])
1388
        date_str = link.string
1389
        text = link.next_sibling.string
1390
        day = string_to_date(remove_st_nd_rd_th_from_date(date_str), "%B %d, %Y")
1391
        comic_img_re = re.compile('^%s/comics/' % cls.url)
1392
        img = soup.find('img', src=comic_img_re)
1393
        return {
1394
            'month': day.month,
1395
            'year': day.year,
1396
            'day': day.day,
1397
            'img': [img.get('src')],
1398
            'title': img.get('title'),
1399
            'text': text,
1400
            'num': num,
1401
        }
1402
1403
1404
class ButterSafe(GenericListableComic):
1405
    """Class to retrieve Butter Safe comics."""
1406
    name = 'butter'
1407
    long_name = 'ButterSafe'
1408
    url = 'http://buttersafe.com'
1409
    get_url_from_archive_element = get_href
1410
    comic_link_re = re.compile('^%s/([0-9]*)/([0-9]*)/([0-9]*)/.*' % url)
1411
1412
    @classmethod
1413
    def get_archive_elements(cls):
1414
        archive_url = urljoin_wrapper(cls.url, 'archive/')
1415
        return reversed(get_soup_at_url(archive_url).find_all('a', href=cls.comic_link_re))
1416
1417
    @classmethod
1418
    def get_comic_info(cls, soup, link):
1419
        """Get information about a particular comics."""
1420
        url = cls.get_url_from_archive_element(link)
1421
        title = link.string
1422
        year, month, day = [int(s) for s in cls.comic_link_re.match(url).groups()]
1423
        img = soup.find('div', id='comic').find('img')
1424
        assert img['alt'] == title
1425
        return {
1426
            'title': title,
1427
            'day': day,
1428
            'month': month,
1429
            'year': year,
1430
            'img': [img['src']],
1431
        }
1432
1433
1434
class CalvinAndHobbes(GenericComic):
1435
    """Class to retrieve Calvin and Hobbes comics."""
1436
    # Also on http://www.gocomics.com/calvinandhobbes/
1437
    name = 'calvin'
1438
    long_name = 'Calvin and Hobbes'
1439
    # This is not through any official webpage but eh...
1440
    url = 'http://marcel-oehler.marcellosendos.ch/comics/ch/'
1441
1442
    @classmethod
1443
    def get_next_comic(cls, last_comic):
1444
        """Generator to get the next comic. Implementation of GenericComic's abstract method."""
1445
        last_date = get_date_for_comic(
1446
            last_comic) if last_comic else date(1985, 11, 1)
1447
        link_re = re.compile('^([0-9]*)/([0-9]*)/')
1448
        img_re = re.compile('')
1449
        for link in get_soup_at_url(cls.url).find_all('a', href=link_re):
1450
            url = link['href']
1451
            year, month = link_re.match(url).groups()
1452 View Code Duplication
            if date(int(year), int(month), 1) + timedelta(days=31) >= last_date:
1453
                img_re = re.compile('^%s%s([0-9]*)' % (year, month))
1454
                month_url = urljoin_wrapper(cls.url, url)
1455
                for img in get_soup_at_url(month_url).find_all('img', src=img_re):
1456
                    img_src = img['src']
1457
                    day = int(img_re.match(img_src).groups()[0])
1458
                    comic_date = date(int(year), int(month), day)
1459
                    if comic_date > last_date:
1460
                        yield {
1461
                            'url': month_url,
1462
                            'year': int(year),
1463
                            'month': int(month),
1464
                            'day': int(day),
1465
                            'img': ['%s%s/%s/%s' % (cls.url, year, month, img_src)],
1466
                        }
1467
                        last_date = comic_date
1468
1469
1470
class AbstruseGoose(GenericListableComic):
1471
    """Class to retrieve AbstruseGoose Comics."""
1472
    name = 'abstruse'
1473
    long_name = 'Abstruse Goose'
1474
    url = 'http://abstrusegoose.com'
1475
    get_url_from_archive_element = get_href
1476
    comic_url_re = re.compile('^%s/([0-9]*)$' % url)
1477
    comic_img_re = re.compile('^%s/strips/.*' % url)
1478
1479
    @classmethod
1480
    def get_archive_elements(cls):
1481
        archive_url = urljoin_wrapper(cls.url, 'archive')
1482
        return get_soup_at_url(archive_url).find_all('a', href=cls.comic_url_re)
1483
1484
    @classmethod
1485
    def get_comic_info(cls, soup, archive_elt):
1486
        comic_url = cls.get_url_from_archive_element(archive_elt)
1487
        num = int(cls.comic_url_re.match(comic_url).groups()[0])
1488
        return {
1489
            'num': num,
1490
            'title': archive_elt.string,
1491
            'img': [soup.find('img', src=cls.comic_img_re)['src']]
1492
        }
1493
1494
1495
class PhDComics(GenericNavigableComic):
1496
    """Class to retrieve PHD Comics."""
1497
    name = 'phd'
1498
    long_name = 'PhD Comics'
1499
    url = 'http://phdcomics.com/comics/archive.php'
1500
1501
    @classmethod
1502
    def get_first_comic_link(cls):
1503
        """Get link to first comics."""
1504
        soup = get_soup_at_url(cls.url)
1505
        img = soup.find('img', src='http://phdcomics.com/comics/images/first_button.gif')
1506
        return None if img is None else img.parent
1507
1508
    @classmethod
1509
    def get_navi_link(cls, last_soup, next_):
1510
        """Get link to next or previous comic."""
1511
        url = 'http://phdcomics.com/comics/images/%s_button.gif' % ('next' if next_ else 'prev')
1512
        img = last_soup.find('img', src=url)
1513
        return None if img is None else img.parent
1514
1515
    @classmethod
1516
    def get_comic_info(cls, soup, link):
1517
        """Get information about a particular comics."""
1518
        title = soup.find('meta', attrs={'name': 'twitter:title'})['content']
1519
        imgs = soup.find_all('meta', property='og:image')
1520
        return {
1521
            'img': [i['content'] for i in imgs],
1522
            'title': title,
1523
        }
1524
1525
1526
class Quarktees(GenericNavigableComic):
1527
    """Class to retrieve the Quarktees comics."""
1528
    name = 'quarktees'
1529
    long_name = 'Quarktees'
1530
    url = 'http://www.quarktees.com/blogs/news'
1531
    get_url_from_link = join_cls_url_to_href
1532
    get_first_comic_link = simulate_first_link
1533
    first_url = 'http://www.quarktees.com/blogs/news/12486621-coming-soon'
1534
1535
    @classmethod
1536
    def get_navi_link(cls, last_soup, next_):
1537
        """Get link to next or previous comic."""
1538
        return last_soup.find('a', id='article-next' if next_ else 'article-prev')
1539
1540
    @classmethod
1541
    def get_comic_info(cls, soup, link):
1542
        """Get information about a particular comics."""
1543
        title = soup.find('meta', property='og:title')['content']
1544
        article = soup.find('div', class_='single-article')
1545
        imgs = article.find_all('img')
1546
        return {
1547
            'title': title,
1548
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
1549
        }
1550
1551
1552
class OverCompensating(GenericNavigableComic):
1553
    """Class to retrieve the Over Compensating comics."""
1554
    name = 'compensating'
1555
    long_name = 'Over Compensating'
1556
    url = 'http://www.overcompensating.com'
1557
    get_url_from_link = join_cls_url_to_href
1558
1559
    @classmethod
1560
    def get_first_comic_link(cls):
1561
        """Get link to first comics."""
1562
        return get_soup_at_url(cls.url).find('a', href=re.compile('comic=1$'))
1563
1564
    @classmethod
1565
    def get_navi_link(cls, last_soup, next_):
1566
        """Get link to next or previous comic."""
1567
        return last_soup.find('a', title='next comic' if next_ else 'go back already')
1568
1569
    @classmethod
1570
    def get_comic_info(cls, soup, link):
1571
        """Get information about a particular comics."""
1572
        img_src_re = re.compile('^/oc/comics/.*')
1573
        comic_num_re = re.compile('.*comic=([0-9]*)$')
1574
        comic_url = cls.get_url_from_link(link)
1575
        num = int(comic_num_re.match(comic_url).groups()[0])
1576
        img = soup.find('img', src=img_src_re)
1577
        return {
1578
            'num': num,
1579
            'img': [urljoin_wrapper(comic_url, img['src'])],
1580
            'title': img.get('title')
1581
        }
1582
1583
1584
class Oglaf(GenericNavigableComic):
1585
    """Class to retrieve Oglaf comics."""
1586
    name = 'oglaf'
1587
    long_name = 'Oglaf [NSFW]'
1588
    url = 'http://oglaf.com'
1589
    _categories = ('NSFW', )
1590
    get_url_from_link = join_cls_url_to_href
1591
1592
    @classmethod
1593
    def get_first_comic_link(cls):
1594
        """Get link to first comics."""
1595
        return get_soup_at_url(cls.url).find("div", id="st").parent
1596
1597
    @classmethod
1598
    def get_navi_link(cls, last_soup, next_):
1599
        """Get link to next or previous comic."""
1600
        div = last_soup.find("div", id="nx" if next_ else "pvs")
1601
        return div.parent if div else None
1602
1603
    @classmethod
1604
    def get_comic_info(cls, soup, link):
1605
        """Get information about a particular comics."""
1606
        title = soup.find('title').string
1607
        title_imgs = soup.find('div', id='tt').find_all('img')
1608
        assert len(title_imgs) == 1, title_imgs
1609
        strip_imgs = soup.find_all('img', id='strip')
1610
        assert len(strip_imgs) == 1, strip_imgs
1611
        imgs = title_imgs + strip_imgs
1612
        desc = ' '.join(i['title'] for i in imgs)
1613
        return {
1614
            'title': title,
1615
            'img': [i['src'] for i in imgs],
1616
            'description': desc,
1617
        }
1618
1619
1620
class ScandinaviaAndTheWorld(GenericNavigableComic):
1621
    """Class to retrieve Scandinavia And The World comics."""
1622
    name = 'satw'
1623
    long_name = 'Scandinavia And The World'
1624
    url = 'http://satwcomic.com'
1625
    get_first_comic_link = simulate_first_link
1626
    first_url = 'http://satwcomic.com/sweden-denmark-and-norway'
1627
1628
    @classmethod
1629
    def get_navi_link(cls, last_soup, next_):
1630
        """Get link to next or previous comic."""
1631
        return last_soup.find('a', accesskey='n' if next_ else 'p')
1632
1633
    @classmethod
1634
    def get_comic_info(cls, soup, link):
1635
        """Get information about a particular comics."""
1636
        title = soup.find('meta', attrs={'name': 'twitter:label1'})['content']
1637
        desc = soup.find('meta', property='og:description')['content']
1638
        imgs = soup.find_all('img', itemprop="image")
1639
        return {
1640
            'title': title,
1641
            'description': desc,
1642
            'img': [i['src'] for i in imgs],
1643
        }
1644
1645
1646
class SomethingOfThatIlk(GenericDeletedComic):
1647
    """Class to retrieve the Something Of That Ilk comics."""
1648
    name = 'somethingofthatilk'
1649
    long_name = 'Something Of That Ilk'
1650
    url = 'http://www.somethingofthatilk.com'
1651
1652
1653
class InfiniteMonkeyBusiness(GenericNavigableComic):
1654
    """Generic class to retrieve InfiniteMonkeyBusiness comics."""
1655
    name = 'monkey'
1656
    long_name = 'Infinite Monkey Business'
1657
    url = 'http://infinitemonkeybusiness.net'
1658
    get_navi_link = get_a_navi_comicnavnext_navinext
1659
    get_first_comic_link = simulate_first_link
1660
    first_url = 'http://infinitemonkeybusiness.net/comic/pillory/'
1661
1662
    @classmethod
1663
    def get_comic_info(cls, soup, link):
1664
        """Get information about a particular comics."""
1665
        title = soup.find('meta', property='og:title')['content']
1666
        imgs = soup.find('div', id='comic').find_all('img')
1667
        return {
1668
            'title': title,
1669
            'img': [i['src'] for i in imgs],
1670
        }
1671
1672
1673
class Wondermark(GenericListableComic):
1674
    """Class to retrieve the Wondermark comics."""
1675
    name = 'wondermark'
1676
    long_name = 'Wondermark'
1677
    url = 'http://wondermark.com'
1678
    get_url_from_archive_element = get_href
1679
1680
    @classmethod
1681
    def get_archive_elements(cls):
1682
        archive_url = urljoin_wrapper(cls.url, 'archive/')
1683
        return reversed(get_soup_at_url(archive_url).find_all('a', rel='bookmark'))
1684
1685
    @classmethod
1686
    def get_comic_info(cls, soup, link):
1687
        """Get information about a particular comics."""
1688
        date_str = soup.find('div', class_='postdate').find('em').string
1689
        day = string_to_date(remove_st_nd_rd_th_from_date(date_str), "%B %d, %Y")
1690
        div = soup.find('div', id='comic')
1691
        if div:
1692
            img = div.find('img')
1693
            img_src = [img['src']]
1694
            alt = img['alt']
1695
            assert alt == img['title']
1696
            title = soup.find('meta', property='og:title')['content']
1697
        else:
1698
            img_src = []
1699
            alt = ''
1700
            title = ''
1701
        return {
1702
            'month': day.month,
1703
            'year': day.year,
1704
            'day': day.day,
1705
            'img': img_src,
1706
            'title': title,
1707
            'alt': alt,
1708
            'tags': ' '.join(t.string for t in soup.find('div', class_='postmeta').find_all('a', rel='tag')),
1709
        }
1710
1711
1712
class WarehouseComic(GenericNavigableComic):
1713
    """Class to retrieve Warehouse Comic comics."""
1714
    name = 'warehouse'
1715
    long_name = 'Warehouse Comic'
1716
    url = 'http://warehousecomic.com'
1717
    get_first_comic_link = get_a_navi_navifirst
1718
    get_navi_link = get_link_rel_next
1719
1720
    @classmethod
1721
    def get_comic_info(cls, soup, link):
1722
        """Get information about a particular comics."""
1723
        title = soup.find('h2', class_='post-title').string
1724
        date_str = soup.find('span', class_='post-date').string
1725
        day = string_to_date(date_str, "%B %d, %Y")
1726
        imgs = soup.find('div', id='comic').find_all('img')
1727
        return {
1728
            'img': [i['src'] for i in imgs],
1729
            'title': title,
1730
            'day': day.day,
1731
            'month': day.month,
1732
            'year': day.year,
1733
        }
1734
1735
1736
class JustSayEh(GenericNavigableComic):
1737
    """Class to retrieve Just Say Eh comics."""
1738
    # Also on http//tapastic.com/series/Just-Say-Eh
1739
    name = 'justsayeh'
1740
    long_name = 'Just Say Eh'
1741
    url = 'http://www.justsayeh.com'
1742
    get_first_comic_link = get_a_navi_navifirst
1743
    get_navi_link = get_a_navi_comicnavnext_navinext
1744
1745
    @classmethod
1746
    def get_comic_info(cls, soup, link):
1747
        """Get information about a particular comics."""
1748
        title = soup.find('h2', class_='post-title').string
1749
        imgs = soup.find("div", id="comic").find_all("img")
1750
        assert all(i['alt'] == i['title'] for i in imgs)
1751
        alt = imgs[0]['alt']
1752
        return {
1753
            'img': [i['src'] for i in imgs],
1754
            'title': title,
1755
            'alt': alt,
1756
        }
1757
1758
1759 View Code Duplication
class MouseBearComedy(GenericComicNotWorking):  # Website has changed
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
1760
    """Class to retrieve Mouse Bear Comedy comics."""
1761
    # Also on http://mousebearcomedy.tumblr.com
1762
    name = 'mousebear'
1763
    long_name = 'Mouse Bear Comedy'
1764
    url = 'http://www.mousebearcomedy.com'
1765
    get_first_comic_link = get_a_navi_navifirst
1766
    get_navi_link = get_a_navi_comicnavnext_navinext
1767
1768
    @classmethod
1769
    def get_comic_info(cls, soup, link):
1770
        """Get information about a particular comics."""
1771
        title = soup.find('h2', class_='post-title').string
1772
        author = soup.find("span", class_="post-author").find("a").string
1773
        date_str = soup.find("span", class_="post-date").string
1774
        day = string_to_date(date_str, '%B %d, %Y')
1775
        imgs = soup.find("div", id="comic").find_all("img")
1776
        assert all(i['alt'] == i['title'] == title for i in imgs)
1777
        return {
1778
            'day': day.day,
1779
            'month': day.month,
1780
            'year': day.year,
1781
            'img': [i['src'] for i in imgs],
1782
            'title': title,
1783
            'author': author,
1784
        }
1785
1786
1787
class BigFootJustice(GenericNavigableComic):
1788
    """Class to retrieve Big Foot Justice comics."""
1789
    # Also on http://tapastic.com/series/bigfoot-justice
1790
    name = 'bigfoot'
1791
    long_name = 'Big Foot Justice'
1792
    url = 'http://bigfootjustice.com'
1793
    get_first_comic_link = get_a_navi_navifirst
1794
    get_navi_link = get_a_navi_comicnavnext_navinext
1795
1796
    @classmethod
1797
    def get_comic_info(cls, soup, link):
1798
        """Get information about a particular comics."""
1799
        imgs = soup.find('div', id='comic').find_all('img')
1800
        assert all(i['title'] == i['alt'] for i in imgs)
1801
        title = ' '.join(i['title'] for i in imgs)
1802
        return {
1803
            'img': [i['src'] for i in imgs],
1804
            'title': title,
1805
        }
1806
1807
1808
class RespawnComic(GenericNavigableComic):
1809
    """Class to retrieve Respawn Comic."""
1810
    # Also on https://respawncomic.tumblr.com
1811
    name = 'respawn'
1812
    long_name = 'Respawn Comic'
1813
    url = 'http://respawncomic.com '
1814
    _categories = ('RESPAWN', )
1815
    get_navi_link = get_a_rel_next
1816
    get_first_comic_link = simulate_first_link
1817
    first_url = 'http://respawncomic.com/comic/c0001/'
1818
1819
    @classmethod
1820
    def get_comic_info(cls, soup, link):
1821
        """Get information about a particular comics."""
1822
        title = soup.find('meta', property='og:title')['content']
1823
        author = soup.find('meta', attrs={'name': 'shareaholic:article_author_name'})['content']
1824
        date_str = soup.find('meta', attrs={'name': 'shareaholic:article_published_time'})['content']
1825
        date_str = date_str[:10]
1826
        day = string_to_date(date_str, "%Y-%m-%d")
1827
        imgs = soup.find_all('meta', property='og:image')
1828
        skip_imgs = {
1829
            'http://respawncomic.com/wp-content/uploads/2016/03/site/HAROLD2.png',
1830
            'http://respawncomic.com/wp-content/uploads/2016/03/site/DEVA.png'
1831
        }
1832
        return {
1833
            'title': title,
1834
            'author': author,
1835
            'day': day.day,
1836
            'month': day.month,
1837
            'year': day.year,
1838
            'img': [i['content'] for i in imgs if i['content'] not in skip_imgs],
1839
        }
1840
1841
1842
class SafelyEndangered(GenericNavigableComic):
1843
    """Class to retrieve Safely Endangered comics."""
1844
    # Also on http://tumblr.safelyendangered.com
1845
    name = 'endangered'
1846
    long_name = 'Safely Endangered'
1847
    url = 'http://www.safelyendangered.com'
1848
    get_navi_link = get_link_rel_next
1849
    get_first_comic_link = simulate_first_link
1850
    first_url = 'http://www.safelyendangered.com/comic/ignored/'
1851
1852
    @classmethod
1853
    def get_comic_info(cls, soup, link):
1854
        """Get information about a particular comics."""
1855
        title = soup.find('h2', class_='post-title').string
1856
        date_str = soup.find('span', class_='post-date').string
1857
        day = string_to_date(date_str, '%B %d, %Y')
1858
        imgs = soup.find('div', id='comic').find_all('img')
1859
        alt = imgs[0]['alt']
1860
        assert all(i['alt'] == i['title'] for i in imgs)
1861
        return {
1862
            'day': day.day,
1863
            'month': day.month,
1864
            'year': day.year,
1865
            'img': [i['src'] for i in imgs],
1866
            'title': title,
1867
            'alt': alt,
1868
        }
1869
1870
1871 View Code Duplication
class PicturesInBoxes(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
1872
    """Class to retrieve Pictures In Boxes comics."""
1873
    # Also on https://picturesinboxescomic.tumblr.com
1874
    name = 'picturesinboxes'
1875
    long_name = 'Pictures in Boxes'
1876
    url = 'http://www.picturesinboxes.com'
1877
    get_navi_link = get_a_navi_navinext
1878
    get_first_comic_link = simulate_first_link
1879
    first_url = 'http://www.picturesinboxes.com/2013/10/26/tetris/'
1880
1881
    @classmethod
1882
    def get_comic_info(cls, soup, link):
1883
        """Get information about a particular comics."""
1884
        title = soup.find('h2', class_='post-title').string
1885
        author = soup.find("span", class_="post-author").find("a").string
1886
        date_str = soup.find('span', class_='post-date').string
1887
        day = string_to_date(date_str, '%B %d, %Y')
1888
        imgs = soup.find('div', class_='comicpane').find_all('img')
1889
        assert imgs
1890
        assert all(i['title'] == i['alt'] == title for i in imgs)
1891
        return {
1892
            'day': day.day,
1893
            'month': day.month,
1894
            'year': day.year,
1895
            'img': [i['src'] for i in imgs],
1896
            'title': title,
1897
            'author': author,
1898
        }
1899
1900
1901
class Penmen(GenericComicNotWorking, GenericNavigableComic):
1902
    """Class to retrieve Penmen comics."""
1903
    name = 'penmen'
1904
    long_name = 'Penmen'
1905
    url = 'http://penmen.com'
1906
    get_navi_link = get_link_rel_next
1907
    get_first_comic_link = simulate_first_link
1908
    first_url = 'http://penmen.com/index.php/2016/09/12/penmen-announces-grin-big-brand-clothing/'
1909
1910
    @classmethod
1911
    def get_comic_info(cls, soup, link):
1912
        """Get information about a particular comics."""
1913
        title = soup.find('title').string
1914
        imgs = soup.find('div', class_='entry-content').find_all('img')
1915
        short_url = soup.find('link', rel='shortlink')['href']
1916
        tags = ' '.join(t.string for t in soup.find_all('a', rel='tag'))
1917
        date_str = soup.find('time')['datetime'][:10]
1918
        day = string_to_date(date_str, "%Y-%m-%d")
1919
        return {
1920
            'title': title,
1921
            'short_url': short_url,
1922
            'img': [i['src'] for i in imgs],
1923
            'tags': tags,
1924
            'month': day.month,
1925
            'year': day.year,
1926
            'day': day.day,
1927
        }
1928
1929
1930
class TheDoghouseDiaries(GenericDeletedComic, GenericNavigableComic):
1931
    """Class to retrieve The Dog House Diaries comics."""
1932
    name = 'doghouse'
1933
    long_name = 'The Dog House Diaries'
1934
    url = 'http://thedoghousediaries.com'
1935
1936
    @classmethod
1937
    def get_first_comic_link(cls):
1938
        """Get link to first comics."""
1939
        return get_soup_at_url(cls.url).find('a', id='firstlink')
1940
1941
    @classmethod
1942
    def get_navi_link(cls, last_soup, next_):
1943
        """Get link to next or previous comic."""
1944
        return last_soup.find('a', id='nextlink' if next_ else 'previouslink')
1945
1946
    @classmethod
1947
    def get_comic_info(cls, soup, link):
1948
        """Get information about a particular comics."""
1949
        comic_img_re = re.compile('^dhdcomics/.*')
1950
        img = soup.find('img', src=comic_img_re)
1951
        comic_url = cls.get_url_from_link(link)
1952
        return {
1953
            'title': soup.find('h2', id='titleheader').string,
1954
            'title2': soup.find('div', id='subtext').string,
1955
            'alt': img.get('title'),
1956
            'img': [urljoin_wrapper(comic_url, img['src'].strip())],
1957
            'num': int(comic_url.split('/')[-1]),
1958
        }
1959 View Code Duplication
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
1960
1961
class InvisibleBread(GenericListableComic):
1962
    """Class to retrieve Invisible Bread comics."""
1963
    # Also on http://www.gocomics.com/invisible-bread
1964
    name = 'invisiblebread'
1965
    long_name = 'Invisible Bread'
1966
    url = 'http://invisiblebread.com'
1967
1968
    @classmethod
1969
    def get_archive_elements(cls):
1970
        archive_url = urljoin_wrapper(cls.url, 'archives/')
1971
        return reversed(get_soup_at_url(archive_url).find_all('td', class_='archive-title'))
1972
1973
    @classmethod
1974
    def get_url_from_archive_element(cls, td):
1975
        return td.find('a')['href']
1976
1977
    @classmethod
1978
    def get_comic_info(cls, soup, td):
1979
        """Get information about a particular comics."""
1980
        url = cls.get_url_from_archive_element(td)
1981
        title = td.find('a').string
1982
        month_and_day = td.previous_sibling.string
1983
        link_re = re.compile('^%s/([0-9]+)/' % cls.url)
1984
        year = link_re.match(url).groups()[0]
1985
        date_str = month_and_day + ' ' + year
1986
        day = string_to_date(date_str, '%b %d %Y')
1987
        imgs = [soup.find('div', id='comic').find('img')]
1988
        assert len(imgs) == 1, imgs
1989
        assert all(i['title'] == i['alt'] == title for i in imgs)
1990
        return {
1991
            'month': day.month,
1992
            'year': day.year,
1993
            'day': day.day,
1994
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
1995
            'title': title,
1996
        }
1997
1998
1999
class DiscoBleach(GenericDeletedComic):
2000
    """Class to retrieve Disco Bleach Comics."""
2001
    name = 'discobleach'
2002
    long_name = 'Disco Bleach'
2003
    url = 'http://discobleach.com'
2004
2005
2006
class TubeyToons(GenericDeletedComic):
2007
    """Class to retrieve TubeyToons comics."""
2008
    # Also on http://tapastic.com/series/Tubey-Toons
2009
    # Also on https://tubeytoons.tumblr.com
2010
    name = 'tubeytoons'
2011
    long_name = 'Tubey Toons'
2012
    url = 'http://tubeytoons.com'
2013
    _categories = ('TUNEYTOONS', )
2014
2015
2016
class CompletelySeriousComics(GenericNavigableComic):
2017
    """Class to retrieve Completely Serious comics."""
2018
    name = 'completelyserious'
2019
    long_name = 'Completely Serious Comics'
2020
    url = 'http://completelyseriouscomics.com'
2021
    get_first_comic_link = get_a_navi_navifirst
2022
    get_navi_link = get_a_navi_navinext
2023
2024
    @classmethod
2025
    def get_comic_info(cls, soup, link):
2026
        """Get information about a particular comics."""
2027
        title = soup.find('h2', class_='post-title').string
2028
        author = soup.find('span', class_='post-author').contents[1].string
2029
        date_str = soup.find('span', class_='post-date').string
2030
        day = string_to_date(date_str, '%B %d, %Y')
2031
        imgs = soup.find('div', class_='comicpane').find_all('img')
2032
        assert imgs
2033
        alt = imgs[0]['title']
2034
        assert all(i['title'] == i['alt'] == alt for i in imgs)
2035
        return {
2036
            'month': day.month,
2037
            'year': day.year,
2038
            'day': day.day,
2039
            'img': [i['src'] for i in imgs],
2040
            'title': title,
2041
            'alt': alt,
2042
            'author': author,
2043
        }
2044
2045
2046 View Code Duplication
class PoorlyDrawnLines(GenericListableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
2047
    """Class to retrieve Poorly Drawn Lines comics."""
2048
    # Also on http://pdlcomics.tumblr.com
2049
    name = 'poorlydrawn'
2050
    long_name = 'Poorly Drawn Lines'
2051
    url = 'https://www.poorlydrawnlines.com'
2052
    _categories = ('POORLYDRAWN', )
2053
    get_url_from_archive_element = get_href
2054
2055
    @classmethod
2056
    def get_comic_info(cls, soup, link):
2057
        """Get information about a particular comics."""
2058
        imgs = soup.find('div', class_='post').find_all('img')
2059
        assert len(imgs) <= 1, imgs
2060
        return {
2061
            'img': [i['src'] for i in imgs],
2062
            'title': imgs[0].get('title', "") if imgs else "",
2063
        }
2064
2065
    @classmethod
2066
    def get_archive_elements(cls):
2067
        archive_url = urljoin_wrapper(cls.url, 'archive')
2068
        url_re = re.compile('^%s/comic/.' % cls.url)
2069
        return reversed(get_soup_at_url(archive_url).find_all('a', href=url_re))
2070
2071
2072
class LoadingComics(GenericNavigableComic):
2073
    """Class to retrieve Loading Artist comics."""
2074
    name = 'loadingartist'
2075
    long_name = 'Loading Artist'
2076
    url = 'http://www.loadingartist.com/latest'
2077
2078
    @classmethod
2079
    def get_first_comic_link(cls):
2080
        """Get link to first comics."""
2081
        return get_soup_at_url(cls.url).find('a', title="First")
2082
2083
    @classmethod
2084
    def get_navi_link(cls, last_soup, next_):
2085
        """Get link to next or previous comic."""
2086
        return last_soup.find('a', title='Next' if next_ else 'Previous')
2087
2088
    @classmethod
2089
    def get_comic_info(cls, soup, link):
2090
        """Get information about a particular comics."""
2091
        title = soup.find('h1').string
2092
        date_str = soup.find('span', class_='date').string.strip()
2093
        day = string_to_date(date_str, "%B %d, %Y")
2094
        imgs = soup.find('div', class_='comic').find_all('img', alt='', title='')
2095
        return {
2096
            'title': title,
2097
            'img': [i['src'] for i in imgs],
2098
            'month': day.month,
2099
            'year': day.year,
2100
            'day': day.day,
2101
        }
2102
2103
2104
class ChuckleADuck(GenericNavigableComic):
2105
    """Class to retrieve Chuckle-A-Duck comics."""
2106
    name = 'chuckleaduck'
2107
    long_name = 'Chuckle-A-duck'
2108
    url = 'http://chuckleaduck.com'
2109
    get_first_comic_link = get_div_navfirst_a
2110
    get_navi_link = get_link_rel_next
2111
2112
    @classmethod
2113
    def get_comic_info(cls, soup, link):
2114
        """Get information about a particular comics."""
2115
        date_str = soup.find('span', class_='post-date').string
2116
        day = string_to_date(remove_st_nd_rd_th_from_date(date_str), "%B %d, %Y")
2117
        author = soup.find('span', class_='post-author').string
2118
        div = soup.find('div', id='comic')
2119
        imgs = div.find_all('img') if div else []
2120
        title = imgs[0]['title'] if imgs else ""
2121
        assert all(i['title'] == i['alt'] == title for i in imgs)
2122
        return {
2123
            'month': day.month,
2124
            'year': day.year,
2125
            'day': day.day,
2126
            'img': [i['src'] for i in imgs],
2127
            'title': title,
2128
            'author': author,
2129
        }
2130
2131
2132
class DepressedAlien(GenericNavigableComic):
2133
    """Class to retrieve Depressed Alien Comics."""
2134
    name = 'depressedalien'
2135
    long_name = 'Depressed Alien'
2136
    url = 'http://depressedalien.com'
2137
    get_url_from_link = join_cls_url_to_href
2138
2139
    @classmethod
2140
    def get_first_comic_link(cls):
2141
        """Get link to first comics."""
2142
        return get_soup_at_url(cls.url).find('img', attrs={'name': 'beginArrow'}).parent
2143
2144 View Code Duplication
    @classmethod
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
2145
    def get_navi_link(cls, last_soup, next_):
2146
        """Get link to next or previous comic."""
2147
        return last_soup.find('img', attrs={'name': 'rightArrow' if next_ else 'leftArrow'}).parent
2148
2149
    @classmethod
2150
    def get_comic_info(cls, soup, link):
2151
        """Get information about a particular comics."""
2152
        title = soup.find('meta', attrs={'name': 'twitter:title'})['content']
2153
        imgs = soup.find_all('meta', property='og:image')
2154
        return {
2155
            'title': title,
2156
            'img': [i['content'] for i in imgs],
2157
        }
2158
2159
2160
class TurnOffUs(GenericListableComic):
2161
    """Class to retrieve TurnOffUs comics."""
2162
    name = 'turnoffus'
2163
    long_name = 'Turn Off Us'
2164
    url = 'http://turnoff.us'
2165
    get_url_from_archive_element = join_cls_url_to_href
2166
2167
    @classmethod
2168
    def get_archive_elements(cls):
2169
        archive_url = urljoin_wrapper(cls.url, 'all')
2170
        post_list = get_soup_at_url(archive_url).find('ul', class_='post-list')
2171
        return reversed(post_list.find_all('a', class_='post-link'))
2172
2173
    @classmethod
2174
    def get_comic_info(cls, soup, archive_elt):
2175
        """Get information about a particular comics."""
2176
        title = soup.find('meta', property='og:title')['content']
2177
        imgs = soup.find_all('meta', property='og:image')
2178
        return {
2179
            'title': title,
2180
            'img': [i['content'] for i in imgs],
2181
        }
2182
2183
2184
class ThingsInSquares(GenericListableComic):
2185
    """Class to retrieve Things In Squares comics."""
2186
    # This can be retrieved in other languages
2187
    # Also on https://tapastic.com/series/Things-in-Squares
2188
    name = 'squares'
2189
    long_name = 'Things in squares'
2190
    url = 'http://www.thingsinsquares.com'
2191
2192
    @classmethod
2193
    def get_comic_info(cls, soup, tr):
2194
        """Get information about a particular comics."""
2195
        _, td2, td3 = tr.find_all('td')
2196
        a = td2.find('a')
2197
        date_str = td3.string
2198
        day = string_to_date(date_str, "%m.%d.%y")
2199
        title = a.string
2200
        title2 = soup.find('meta', property='og:title')['content']
2201
        desc = soup.find('meta', property='og:description')
2202
        description = desc['content'] if desc else ''
2203
        tags = ' '.join(t['content'] for t in soup.find_all('meta', property='article:tag'))
2204
        imgs = soup.find_all('meta', property='og:image')
2205
        return {
2206
            'day': day.day,
2207
            'month': day.month,
2208
            'year': day.year,
2209
            'title': title,
2210
            'title2': title2,
2211
            'description': description,
2212
            'tags': tags,
2213
            'img': [i['content'] for i in imgs],
2214
        }
2215
2216
    @classmethod
2217
    def get_url_from_archive_element(cls, tr):
2218
        _, td2, __ = tr.find_all('td')
2219
        return td2.find('a')['href']
2220
2221
    @classmethod
2222
    def get_archive_elements(cls):
2223
        archive_url = urljoin_wrapper(cls.url, 'archive-2')
2224
        return reversed(get_soup_at_url(archive_url).find('tbody').find_all('tr'))
2225
2226
2227
class HappleTea(GenericNavigableComic):
2228
    """Class to retrieve Happle Tea Comics."""
2229
    name = 'happletea'
2230
    long_name = 'Happle Tea'
2231
    url = 'http://www.happletea.com'
2232
    get_first_comic_link = get_a_navi_navifirst
2233
    get_navi_link = get_link_rel_next
2234
2235
    @classmethod
2236
    def get_comic_info(cls, soup, link):
2237
        """Get information about a particular comics."""
2238
        imgs = soup.find('div', id='comic').find_all('img')
2239
        post = soup.find('div', class_='post-content')
2240
        title = post.find('h2', class_='post-title').string
2241
        author = post.find('a', rel='author').string
2242
        date_str = post.find('span', class_='post-date').string
2243
        day = string_to_date(date_str, "%B %d, %Y")
2244
        assert all(i['alt'] == i['title'] for i in imgs)
2245
        return {
2246
            'title': title,
2247
            'img': [i['src'] for i in imgs],
2248
            'alt': ''.join(i['alt'] for i in imgs),
2249
            'month': day.month,
2250
            'year': day.year,
2251
            'day': day.day,
2252
            'author': author,
2253
        }
2254
2255
2256
class RockPaperScissors(GenericNavigableComic):
2257
    """Class to retrieve Rock Paper Scissors comics."""
2258
    name = 'rps'
2259
    long_name = 'Rock Paper Scissors'
2260
    url = 'http://rps-comics.com'
2261
    get_first_comic_link = get_a_navi_navifirst
2262
    get_navi_link = get_link_rel_next
2263
2264
    @classmethod
2265
    def get_comic_info(cls, soup, link):
2266
        """Get information about a particular comics."""
2267
        title = soup.find('title').string
2268
        imgs = soup.find_all('meta', property='og:image')
2269
        short_url = soup.find('link', rel='shortlink')['href']
2270
        transcript = soup.find('div', id='transcript-content').string
2271
        return {
2272
            'title': title,
2273
            'transcript': transcript,
2274
            'short_url': short_url,
2275
            'img': [i['content'] for i in imgs],
2276
        }
2277
2278
2279
class FatAwesomeComics(GenericNavigableComic):
2280
    """Class to retrieve Fat Awesome Comics."""
2281
    # Also on http://fatawesomecomedy.tumblr.com
2282
    name = 'fatawesome'
2283
    long_name = 'Fat Awesome'
2284
    url = 'http://fatawesome.com/comics'
2285
    get_navi_link = get_a_rel_next
2286
    get_first_comic_link = simulate_first_link
2287
    first_url = 'http://fatawesome.com/shortbus/'
2288
2289
    @classmethod
2290
    def get_comic_info(cls, soup, link):
2291
        """Get information about a particular comics."""
2292
        title = soup.find('meta', attrs={'name': 'twitter:title'})['content']
2293
        description = soup.find('meta', attrs={'name': 'description'})['content']
2294
        tags_prop = soup.find('meta', property='article:tag')
2295
        tags = tags_prop['content'] if tags_prop else ""
2296
        date_str = soup.find('meta', property='article:published_time')['content'][:10]
2297
        day = string_to_date(date_str, "%Y-%m-%d")
2298
        imgs = soup.find_all('img', attrs={'data-recalc-dims': "1"})
2299
        assert len(imgs) == 1, imgs
2300
        return {
2301
            'title': title,
2302
            'description': description,
2303
            'tags': tags,
2304
            'alt': "".join(i['alt'] for i in imgs),
2305
            'img': [i['src'].rsplit('?', 1)[0] for i in imgs],
2306
            'month': day.month,
2307
            'year': day.year,
2308
            'day': day.day,
2309
        }
2310
2311
2312
class JuliasDrawings(GenericListableComic):
2313
    """Class to retrieve Julia's Drawings."""
2314
    name = 'julia'
2315
    long_name = "Julia's Drawings"
2316
    url = 'https://drawings.jvns.ca'
2317
    get_url_from_archive_element = get_href
2318
2319
    @classmethod
2320
    def get_archive_elements(cls):
2321
        articles = get_soup_at_url(cls.url).find_all('article', class_='li post')
2322
        return [art.find('a') for art in reversed(articles)]
2323
2324
    @classmethod
2325
    def get_comic_info(cls, soup, archive_elt):
2326
        """Get information about a particular comics."""
2327
        date_str = soup.find('meta', property='og:article:published_time')['content'][:10]
2328
        day = string_to_date(date_str, "%Y-%m-%d")
2329
        title = soup.find('h3', class_='p-post-title').string
2330
        imgs = soup.find('section', class_='post-content').find_all('img')
2331
        return {
2332
            'title': title,
2333
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
2334
            'month': day.month,
2335
            'year': day.year,
2336
            'day': day.day,
2337
        }
2338
2339
2340
class AnythingComic(GenericListableComic):
2341
    """Class to retrieve Anything Comics."""
2342 View Code Duplication
    # Also on http://tapastic.com/series/anything
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
2343
    name = 'anythingcomic'
2344
    long_name = 'Anything Comic'
2345
    url = 'http://www.anythingcomic.com'
2346
2347
    @classmethod
2348
    def get_archive_elements(cls):
2349
        archive_url = urljoin_wrapper(cls.url, 'archive/')
2350
        # The first 2 <tr>'s do not correspond to comics
2351
        return get_soup_at_url(archive_url).find('table', id='chapter_table').find_all('tr')[2:]
2352
2353
    @classmethod
2354
    def get_url_from_archive_element(cls, tr):
2355
        """Get url corresponding to an archive element."""
2356
        _, td_comic, td_date, _ = tr.find_all('td')
2357
        link = td_comic.find('a')
2358
        return urljoin_wrapper(cls.url, link['href'])
2359
2360
    @classmethod
2361
    def get_comic_info(cls, soup, tr):
2362
        """Get information about a particular comics."""
2363
        td_num, td_comic, td_date, _ = tr.find_all('td')
2364
        num = int(td_num.string)
2365
        link = td_comic.find('a')
2366
        title = link.string
2367
        imgs = soup.find_all('img', id='comic_image')
2368
        date_str = td_date.string
2369
        day = string_to_date(remove_st_nd_rd_th_from_date(date_str), "%B %d, %Y, %I:%M %p")
2370
        assert len(imgs) == 1, imgs
2371
        assert all(i.get('alt') == i.get('title') for i in imgs)
2372
        return {
2373
            'num': num,
2374
            'title': title,
2375
            'alt': imgs[0].get('alt', ''),
2376
            'img': [i['src'] for i in imgs],
2377
            'month': day.month,
2378
            'year': day.year,
2379
            'day': day.day,
2380
        }
2381
2382
2383
class LonnieMillsap(GenericNavigableComic):
2384
    """Class to retrieve Lonnie Millsap's comics."""
2385
    name = 'millsap'
2386
    long_name = 'Lonnie Millsap'
2387
    url = 'http://www.lonniemillsap.com'
2388
    get_navi_link = get_link_rel_next
2389
    get_first_comic_link = simulate_first_link
2390
    first_url = 'http://www.lonniemillsap.com/?p=42'
2391
2392
    @classmethod
2393
    def get_comic_info(cls, soup, link):
2394
        """Get information about a particular comics."""
2395
        title = soup.find('h2', class_='post-title').string
2396
        post = soup.find('div', class_='post-content')
2397
        author = post.find("span", class_="post-author").find("a").string
2398
        date_str = post.find("span", class_="post-date").string
2399
        day = string_to_date(date_str, "%B %d, %Y")
2400
        imgs = post.find("div", class_="entry").find_all("img")
2401
        return {
2402
            'title': title,
2403
            'author': author,
2404
            'img': [i['src'] for i in imgs],
2405
            'month': day.month,
2406
            'year': day.year,
2407
            'day': day.day,
2408
        }
2409
2410
2411 View Code Duplication
class LinsEditions(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
2412
    """Class to retrieve L.I.N.S. Editions comics."""
2413
    # Also on https://linscomics.tumblr.com
2414
    # Now on https://warandpeas.com
2415
    name = 'lins'
2416
    long_name = 'L.I.N.S. Editions'
2417
    url = 'https://linsedition.com'
2418
    _categories = ('LINS', )
2419
    get_navi_link = get_link_rel_next
2420
    get_first_comic_link = simulate_first_link
2421
    first_url = 'https://linsedition.com/2011/09/07/l-i-n-s/'
2422
2423
    @classmethod
2424
    def get_comic_info(cls, soup, link):
2425
        """Get information about a particular comics."""
2426
        title = soup.find('meta', property='og:title')['content']
2427
        imgs = soup.find_all('meta', property='og:image')
2428
        date_str = soup.find('meta', property='article:published_time')['content'][:10]
2429
        day = string_to_date(date_str, "%Y-%m-%d")
2430
        return {
2431
            'title': title,
2432
            'img': [i['content'] for i in imgs],
2433
            'month': day.month,
2434
            'year': day.year,
2435
            'day': day.day,
2436
        }
2437
2438
2439
class ThorsThundershack(GenericNavigableComic):
2440
    """Class to retrieve Thor's Thundershack comics."""
2441
    # Also on http://tapastic.com/series/Thors-Thundershac
2442
    name = 'thor'
2443
    long_name = 'Thor\'s Thundershack'
2444
    url = 'http://www.thorsthundershack.com'
2445
    _categories = ('THOR', )
2446
    get_url_from_link = join_cls_url_to_href
2447
2448
    @classmethod
2449
    def get_first_comic_link(cls):
2450
        """Get link to first comics."""
2451
        return get_soup_at_url(cls.url).find('a', class_='first navlink')
2452
2453
    @classmethod
2454
    def get_navi_link(cls, last_soup, next_):
2455
        """Get link to next or previous comic."""
2456
        for link in last_soup.find_all('a', rel='next' if next_ else 'prev'):
2457
            if link['href'] != '/comic':
2458
                return link
2459
        return None
2460
2461
    @classmethod
2462
    def get_comic_info(cls, soup, link):
2463
        """Get information about a particular comics."""
2464
        title = soup.find('meta', attrs={'name': 'description'})["content"]
2465
        description = soup.find('div', itemprop='articleBody').text
2466
        author = soup.find('span', itemprop='author copyrightHolder').string
2467
        imgs = soup.find_all('img', itemprop='image')
2468
        assert all(i['title'] == i['alt'] for i in imgs)
2469
        alt = imgs[0]['alt'] if imgs else ""
2470
        date_str = soup.find('time', itemprop='datePublished')["datetime"]
2471
        day = string_to_date(date_str, "%Y-%m-%d %H:%M:%S")
2472
        return {
2473
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
2474
            'month': day.month,
2475
            'year': day.year,
2476
            'day': day.day,
2477
            'author': author,
2478
            'title': title,
2479
            'alt': alt,
2480
            'description': description,
2481
        }
2482
2483
2484 View Code Duplication
class GerbilWithAJetpack(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
2485
    """Class to retrieve GerbilWithAJetpack comics."""
2486
    name = 'gerbil'
2487
    long_name = 'Gerbil With A Jetpack'
2488
    url = 'http://gerbilwithajetpack.com'
2489
    get_first_comic_link = get_a_navi_navifirst
2490
    get_navi_link = get_a_rel_next
2491
2492
    @classmethod
2493
    def get_comic_info(cls, soup, link):
2494
        """Get information about a particular comics."""
2495
        title = soup.find('h2', class_='post-title').string
2496
        author = soup.find("span", class_="post-author").find("a").string
2497
        date_str = soup.find("span", class_="post-date").string
2498
        day = string_to_date(date_str, "%B %d, %Y")
2499
        imgs = soup.find("div", id="comic").find_all("img")
2500
        alt = imgs[0]['alt']
2501
        assert all(i['alt'] == i['title'] == alt for i in imgs)
2502
        return {
2503
            'img': [i['src'] for i in imgs],
2504
            'title': title,
2505
            'alt': alt,
2506
            'author': author,
2507
            'day': day.day,
2508
            'month': day.month,
2509
            'year': day.year
2510
        }
2511
2512
2513 View Code Duplication
class EveryDayBlues(GenericDeletedComic, GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
2514
    """Class to retrieve EveryDayBlues Comics."""
2515
    name = "blues"
2516
    long_name = "Every Day Blues"
2517
    url = "http://everydayblues.net"
2518
    get_first_comic_link = get_a_navi_navifirst
2519
    get_navi_link = get_link_rel_next
2520
2521
    @classmethod
2522
    def get_comic_info(cls, soup, link):
2523
        """Get information about a particular comics."""
2524
        title = soup.find("h2", class_="post-title").string
2525
        author = soup.find("span", class_="post-author").find("a").string
2526
        date_str = soup.find("span", class_="post-date").string
2527
        day = string_to_date(date_str, "%d. %B %Y", "de_DE.utf8")
2528
        imgs = soup.find("div", id="comic").find_all("img")
2529
        assert all(i['alt'] == i['title'] == title for i in imgs)
2530
        assert len(imgs) <= 1, imgs
2531
        return {
2532
            'img': [i['src'] for i in imgs],
2533
            'title': title,
2534
            'author': author,
2535
            'day': day.day,
2536
            'month': day.month,
2537
            'year': day.year
2538
        }
2539
2540
2541
class BiterComics(GenericNavigableComic):
2542
    """Class to retrieve Biter Comics."""
2543
    name = "biter"
2544
    long_name = "Biter Comics"
2545
    url = "http://www.bitercomics.com"
2546
    get_first_comic_link = get_a_navi_navifirst
2547
    get_navi_link = get_link_rel_next
2548
2549
    @classmethod
2550
    def get_comic_info(cls, soup, link):
2551
        """Get information about a particular comics."""
2552
        title = soup.find("h1", class_="entry-title").string
2553
        author = soup.find("span", class_="author vcard").find("a").string
2554
        date_str = soup.find("span", class_="entry-date").string
2555
        day = string_to_date(date_str, "%B %d, %Y")
2556
        imgs = soup.find("div", id="comic").find_all("img")
2557
        assert all(i['alt'] == i['title'] for i in imgs)
2558
        assert len(imgs) == 1, imgs
2559
        alt = imgs[0]['alt']
2560
        return {
2561
            'img': [i['src'] for i in imgs],
2562
            'title': title,
2563
            'alt': alt,
2564
            'author': author,
2565
            'day': day.day,
2566
            'month': day.month,
2567
            'year': day.year
2568
        }
2569
2570
2571 View Code Duplication
class TheAwkwardYeti(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
2572
    """Class to retrieve The Awkward Yeti comics."""
2573
    # Also on http://www.gocomics.com/the-awkward-yeti
2574
    # Also on http://larstheyeti.tumblr.com
2575
    # Also on https://tapastic.com/series/TheAwkwardYeti
2576
    name = 'yeti'
2577
    long_name = 'The Awkward Yeti'
2578
    url = 'http://theawkwardyeti.com'
2579
    _categories = ('YETI', )
2580
    get_first_comic_link = get_a_navi_navifirst
2581
    get_navi_link = get_link_rel_next
2582
2583
    @classmethod
2584
    def get_comic_info(cls, soup, link):
2585
        """Get information about a particular comics."""
2586
        title = soup.find('h2', class_='post-title').string
2587
        date_str = soup.find("span", class_="post-date").string
2588
        day = string_to_date(date_str, "%B %d, %Y")
2589
        imgs = soup.find("div", id="comic").find_all("img")
2590
        assert all(idx > 0 or i['alt'] == i['title'] for idx, i in enumerate(imgs))
2591
        return {
2592
            'img': [i['src'] for i in imgs],
2593
            'title': title,
2594
            'day': day.day,
2595
            'month': day.month,
2596
            'year': day.year
2597
        }
2598
2599
2600
class PleasantThoughts(GenericNavigableComic):
2601
    """Class to retrieve Pleasant Thoughts comics."""
2602
    name = 'pleasant'
2603
    long_name = 'Pleasant Thoughts'
2604
    url = 'http://pleasant-thoughts.com'
2605
    get_first_comic_link = get_a_navi_navifirst
2606
    get_navi_link = get_link_rel_next
2607
2608
    @classmethod
2609
    def get_comic_info(cls, soup, link):
2610
        """Get information about a particular comics."""
2611
        post = soup.find('div', class_='post-content')
2612
        title = post.find('h2', class_='post-title').string
2613
        imgs = post.find("div", class_="entry").find_all("img")
2614
        return {
2615
            'title': title,
2616
            'img': [i['src'] for i in imgs],
2617
        }
2618
2619
2620
class MisterAndMe(GenericNavigableComic):
2621
    """Class to retrieve Mister & Me Comics."""
2622
    # Also on http://www.gocomics.com/mister-and-me
2623
    # Also on https://tapastic.com/series/Mister-and-Me
2624
    name = 'mister'
2625
    long_name = 'Mister & Me'
2626
    url = 'http://www.mister-and-me.com'
2627
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
2628
    get_navi_link = get_link_rel_next
2629
2630
    @classmethod
2631
    def get_comic_info(cls, soup, link):
2632
        """Get information about a particular comics."""
2633
        title = soup.find('h2', class_='post-title').string
2634
        author = soup.find("span", class_="post-author").find("a").string
2635
        date_str = soup.find("span", class_="post-date").string
2636
        day = string_to_date(date_str, "%B %d, %Y")
2637
        imgs = soup.find("div", id="comic").find_all("img")
2638
        assert all(i['alt'] == i['title'] for i in imgs)
2639
        assert len(imgs) <= 1, imgs
2640
        alt = imgs[0]['alt'] if imgs else ""
2641
        return {
2642
            'img': [i['src'] for i in imgs],
2643
            'title': title,
2644
            'alt': alt,
2645
            'author': author,
2646
            'day': day.day,
2647
            'month': day.month,
2648
            'year': day.year
2649
        }
2650
2651
2652
class LastPlaceComics(GenericNavigableComic):
2653
    """Class to retrieve Last Place Comics."""
2654
    name = 'lastplace'
2655
    long_name = 'Last Place Comics'
2656
    url = "http://lastplacecomics.com"
2657
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
2658
    get_navi_link = get_link_rel_next
2659
2660
    @classmethod
2661
    def get_comic_info(cls, soup, link):
2662
        """Get information about a particular comics."""
2663
        title = soup.find('h2', class_='post-title').string
2664
        author = soup.find("span", class_="post-author").find("a").string
2665
        date_str = soup.find("span", class_="post-date").string
2666
        day = string_to_date(date_str, "%B %d, %Y")
2667
        imgs = soup.find("div", id="comic").find_all("img")
2668
        assert all(i['alt'] == i['title'] for i in imgs)
2669
        assert len(imgs) <= 1, imgs
2670
        alt = imgs[0]['alt'] if imgs else ""
2671
        return {
2672
            'img': [i['src'] for i in imgs],
2673
            'title': title,
2674
            'alt': alt,
2675
            'author': author,
2676
            'day': day.day,
2677
            'month': day.month,
2678
            'year': day.year
2679
        }
2680
2681
2682
class TalesOfAbsurdity(GenericNavigableComic):
2683
    """Class to retrieve Tales Of Absurdity comics."""
2684
    # Also on http://tapastic.com/series/Tales-Of-Absurdity
2685
    # Also on http://talesofabsurdity.tumblr.com
2686
    name = 'absurdity'
2687
    long_name = 'Tales of Absurdity'
2688
    url = 'http://talesofabsurdity.com'
2689
    _categories = ('ABSURDITY', )
2690
    get_first_comic_link = get_a_navi_navifirst
2691
    get_navi_link = get_a_navi_comicnavnext_navinext
2692
2693
    @classmethod
2694
    def get_comic_info(cls, soup, link):
2695
        """Get information about a particular comics."""
2696
        title = soup.find('h2', class_='post-title').string
2697
        author = soup.find("span", class_="post-author").find("a").string
2698
        date_str = soup.find("span", class_="post-date").string
2699
        day = string_to_date(date_str, "%B %d, %Y")
2700
        imgs = soup.find("div", id="comic").find_all("img")
2701
        assert all(i['alt'] == i['title'] for i in imgs)
2702
        alt = imgs[0]['alt'] if imgs else ""
2703
        return {
2704
            'img': [i['src'] for i in imgs],
2705
            'title': title,
2706
            'alt': alt,
2707
            'author': author,
2708
            'day': day.day,
2709
            'month': day.month,
2710
            'year': day.year
2711
        }
2712
2713
2714
class EndlessOrigami(GenericComicNotWorking, GenericNavigableComic):  # Nav not working
2715
    """Class to retrieve Endless Origami Comics."""
2716
    name = "origami"
2717
    long_name = "Endless Origami"
2718
    url = "http://endlessorigami.com"
2719
    get_first_comic_link = get_a_navi_navifirst
2720
    get_navi_link = get_link_rel_next
2721
2722
    @classmethod
2723
    def get_comic_info(cls, soup, link):
2724
        """Get information about a particular comics."""
2725
        title = soup.find('h2', class_='post-title').string
2726
        author = soup.find("span", class_="post-author").find("a").string
2727
        date_str = soup.find("span", class_="post-date").string
2728
        day = string_to_date(date_str, "%B %d, %Y")
2729
        imgs = soup.find("div", id="comic").find_all("img")
2730
        assert all(i['alt'] == i['title'] for i in imgs)
2731
        alt = imgs[0]['alt'] if imgs else ""
2732
        return {
2733
            'img': [i['src'] for i in imgs],
2734
            'title': title,
2735
            'alt': alt,
2736
            'author': author,
2737
            'day': day.day,
2738
            'month': day.month,
2739
            'year': day.year
2740
        }
2741
2742
2743
class PlanC(GenericNavigableComic):
2744
    """Class to retrieve Plan C comics."""
2745
    name = 'planc'
2746
    long_name = 'Plan C'
2747
    url = 'http://www.plancomic.com'
2748
    get_first_comic_link = get_a_navi_navifirst
2749
    get_navi_link = get_a_navi_comicnavnext_navinext
2750
2751
    @classmethod
2752
    def get_comic_info(cls, soup, link):
2753
        """Get information about a particular comics."""
2754
        title = soup.find('h2', class_='post-title').string
2755
        date_str = soup.find("span", class_="post-date").string
2756
        day = string_to_date(date_str, "%B %d, %Y")
2757
        imgs = soup.find('div', id='comic').find_all('img')
2758
        return {
2759
            'title': title,
2760
            'img': [i['src'] for i in imgs],
2761
            'month': day.month,
2762
            'year': day.year,
2763
            'day': day.day,
2764
        }
2765
2766
2767
class BuniComic(GenericNavigableComic):
2768
    """Class to retrieve Buni Comics."""
2769
    name = 'buni'
2770
    long_name = 'BuniComics'
2771 View Code Duplication
    url = 'http://www.bunicomic.com'
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
2772
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
2773
    get_navi_link = get_link_rel_next
2774
2775
    @classmethod
2776
    def get_comic_info(cls, soup, link):
2777
        """Get information about a particular comics."""
2778
        imgs = soup.find('div', id='comic').find_all('img')
2779
        assert all(i['alt'] == i['title'] for i in imgs)
2780
        assert len(imgs) == 1, imgs
2781
        return {
2782
            'img': [i['src'] for i in imgs],
2783
            'title': imgs[0]['title'],
2784
        }
2785
2786
2787
class GenericCommitStrip(GenericNavigableComic):
2788
    """Generic class to retrieve Commit Strips in different languages."""
2789
    get_navi_link = get_a_rel_next
2790
    get_first_comic_link = simulate_first_link
2791
    first_url = NotImplemented
2792
2793
    @classmethod
2794
    def get_comic_info(cls, soup, link):
2795
        """Get information about a particular comics."""
2796
        desc = soup.find('meta', property='og:description')['content']
2797
        title = soup.find('meta', property='og:title')['content']
2798
        imgs = soup.find('div', class_='entry-content').find_all('img')
2799
        title2 = ' '.join(i.get('title', '') for i in imgs)
2800
        return {
2801
            'title': title,
2802
            'title2': title2,
2803
            'description': desc,
2804
            'img': [urljoin_wrapper(cls.url, convert_iri_to_plain_ascii_uri(i['src'])) for i in imgs],
2805
        }
2806
2807
2808
class CommitStripFr(GenericCommitStrip):
2809
    """Class to retrieve Commit Strips in French."""
2810
    name = 'commit_fr'
2811
    long_name = 'Commit Strip (Fr)'
2812
    url = 'http://www.commitstrip.com/fr'
2813
    _categories = ('FRANCAIS', )
2814
    first_url = 'http://www.commitstrip.com/fr/2012/02/22/interview/'
2815
2816
2817
class CommitStripEn(GenericCommitStrip):
2818
    """Class to retrieve Commit Strips in English."""
2819
    name = 'commit_en'
2820
    long_name = 'Commit Strip (En)'
2821
    url = 'http://www.commitstrip.com/en'
2822
    first_url = 'http://www.commitstrip.com/en/2012/02/22/interview/'
2823
2824
2825 View Code Duplication
class GenericBoumerie(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
2826
    """Generic class to retrieve Boumeries comics in different languages."""
2827
    # Also on http://boumeries.tumblr.com
2828
    get_first_comic_link = get_a_navi_navifirst
2829
    get_navi_link = get_link_rel_next
2830
    date_format = NotImplemented
2831
    lang = NotImplemented
2832
2833
    @classmethod
2834
    def get_comic_info(cls, soup, link):
2835
        """Get information about a particular comics."""
2836
        title = soup.find('h2', class_='post-title').string
2837
        short_url = soup.find('link', rel='shortlink')['href']
2838
        author = soup.find("span", class_="post-author").find("a").string
2839
        date_str = soup.find('span', class_='post-date').string
2840
        day = string_to_date(date_str, cls.date_format, cls.lang)
2841
        imgs = soup.find('div', id='comic').find_all('img')
2842
        assert all(i['alt'] == i['title'] for i in imgs)
2843
        return {
2844
            'short_url': short_url,
2845
            'img': [i['src'] for i in imgs],
2846
            'title': title,
2847
            'author': author,
2848
            'month': day.month,
2849
            'year': day.year,
2850
            'day': day.day,
2851
        }
2852
2853
2854
class BoumerieEn(GenericBoumerie):
2855
    """Class to retrieve Boumeries comics in English."""
2856
    name = 'boumeries_en'
2857
    long_name = 'Boumeries (En)'
2858
    url = 'http://comics.boumerie.com'
2859
    _categories = ('BOUMERIES', )
2860
    date_format = "%B %d, %Y"
2861
    lang = 'en_GB.UTF-8'
2862
2863
2864
class BoumerieFr(GenericBoumerie):
2865
    """Class to retrieve Boumeries comics in French."""
2866
    name = 'boumeries_fr'
2867
    long_name = 'Boumeries (Fr)'
2868
    url = 'http://bd.boumerie.com'
2869
    _categories = ('BOUMERIES', 'FRANCAIS')
2870
    date_format = "%B %d, %Y"  # Used to be "%A, %d %B %Y"
2871
    lang = "fr_FR.utf8"
2872
2873
2874 View Code Duplication
class UnearthedComics(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
2875
    """Class to retrieve Unearthed comics."""
2876
    # Also on http://tapastic.com/series/UnearthedComics
2877
    # Also on https://unearthedcomics.tumblr.com
2878
    name = 'unearthed'
2879
    long_name = 'Unearthed Comics'
2880
    url = 'http://unearthedcomics.com'
2881
    _categories = ('UNEARTHED', )
2882
    get_navi_link = get_link_rel_next
2883
    get_first_comic_link = simulate_first_link
2884
    first_url = 'http://unearthedcomics.com/comics/world-with-turn-signals/'
2885
2886
    @classmethod
2887
    def get_comic_info(cls, soup, link):
2888
        """Get information about a particular comics."""
2889
        short_url = soup.find('link', rel='shortlink')['href']
2890
        title_elt = soup.find('h1') or soup.find('h2')
2891
        title = title_elt.string if title_elt else ""
2892
        desc = soup.find('meta', property='og:description')
2893
        date_str = soup.find('time', class_='published updated hidden')['datetime']
2894
        day = string_to_date(date_str, "%Y-%m-%d")
2895
        post = soup.find('div', class_="entry content entry-content type-portfolio")
2896
        imgs = post.find_all('img')
2897
        return {
2898
            'title': title,
2899
            'description': desc,
2900
            'url2': short_url,
2901
            'img': [i['src'] for i in imgs],
2902
            'month': day.month,
2903
            'year': day.year,
2904
            'day': day.day,
2905
        }
2906
2907
2908
class Optipess(GenericNavigableComic):
2909
    """Class to retrieve Optipess comics."""
2910
    name = 'optipess'
2911
    long_name = 'Optipess'
2912
    url = 'http://www.optipess.com'
2913
    get_first_comic_link = get_a_navi_navifirst
2914
    get_navi_link = get_link_rel_next
2915
2916
    @classmethod
2917
    def get_comic_info(cls, soup, link):
2918
        """Get information about a particular comics."""
2919
        title = soup.find('h2', class_='post-title').string
2920
        author = soup.find("span", class_="post-author").find("a").string
2921
        comic = soup.find('div', id='comic')
2922
        imgs = comic.find_all('img') if comic else []
2923
        alt = imgs[0]['title'] if imgs else ""
2924
        assert all(i['alt'] == i['title'] == alt for i in imgs)
2925
        date_str = soup.find('span', class_='post-date').string
2926
        day = string_to_date(date_str, "%B %d, %Y")
2927
        return {
2928
            'title': title,
2929
            'alt': alt,
2930
            'author': author,
2931
            'img': [i['src'] for i in imgs],
2932
            'month': day.month,
2933
            'year': day.year,
2934
            'day': day.day,
2935
        }
2936
2937
2938
class PainTrainComic(GenericNavigableComic):
2939
    """Class to retrieve Pain Train Comics."""
2940
    name = 'paintrain'
2941
    long_name = 'Pain Train Comics'
2942
    url = 'http://paintraincomic.com'
2943
    get_first_comic_link = get_a_navi_navifirst
2944
    get_navi_link = get_link_rel_next
2945
2946
    @classmethod
2947
    def get_comic_info(cls, soup, link):
2948
        """Get information about a particular comics."""
2949
        title = soup.find('h2', class_='post-title').string
2950
        short_url = soup.find('link', rel='shortlink')['href']
2951
        short_url_re = re.compile('^%s/\\?p=([0-9]*)' % cls.url)
2952
        num = int(short_url_re.match(short_url).groups()[0])
2953
        imgs = soup.find('div', id='comic').find_all('img')
2954
        alt = imgs[0]['title']
2955
        assert all(i['alt'] == i['title'] == alt for i in imgs)
2956
        date_str = soup.find('span', class_='post-date').string
2957
        day = string_to_date(date_str, "%d/%m/%Y")
2958
        return {
2959
            'short_url': short_url,
2960
            'num': num,
2961
            'img': [i['src'] for i in imgs],
2962
            'month': day.month,
2963
            'year': day.year,
2964
            'day': day.day,
2965
            'alt': alt,
2966
            'title': title,
2967
        }
2968
2969
2970
class MoonBeard(GenericNavigableComic):
2971
    """Class to retrieve MoonBeard comics."""
2972
    # Also on http://squireseses.tumblr.com
2973
    # Also on http://www.webtoons.com/en/comedy/moon-beard/list?title_no=471
2974
    name = 'moonbeard'
2975
    long_name = 'Moon Beard'
2976
    url = 'http://moonbeard.com'
2977
    _categories = ('MOONBEARD', )
2978
    get_first_comic_link = get_a_navi_navifirst
2979
    get_navi_link = get_a_navi_navinext
2980
2981
    @classmethod
2982
    def get_comic_info(cls, soup, link):
2983
        """Get information about a particular comics."""
2984
        title = soup.find('h2', class_='post-title').string
2985
        short_url = soup.find('link', rel='shortlink')['href']
2986
        short_url_re = re.compile('^%s/\\?p=([0-9]*)' % cls.url)
2987
        num = int(short_url_re.match(short_url).groups()[0])
2988
        imgs = soup.find('div', id='comic').find_all('img')
2989
        alt = imgs[0]['title']
2990
        assert all(i['alt'] == i['title'] == alt for i in imgs)
2991
        date_str = soup.find('span', class_='post-date').string
2992
        day = string_to_date(date_str, "%B %d, %Y")
2993
        tags = ' '.join(t['content'] for t in soup.find_all('meta', property='article:tag'))
2994
        author = soup.find('span', class_='post-author').string
2995
        return {
2996
            'short_url': short_url,
2997
            'num': num,
2998
            'img': [i['src'] for i in imgs],
2999 View Code Duplication
            'month': day.month,
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
3000
            'year': day.year,
3001
            'day': day.day,
3002
            'title': title,
3003
            'tags': tags,
3004
            'alt': alt,
3005
            'author': author,
3006
        }
3007
3008
3009
class SystemComic(GenericNavigableComic):
3010
    """Class to retrieve System Comic."""
3011
    name = 'system'
3012
    long_name = 'System Comic'
3013
    url = 'http://www.systemcomic.com'
3014
    get_navi_link = get_a_rel_next
3015
3016
    @classmethod
3017
    def get_first_comic_link(cls):
3018
        """Get link to first comics."""
3019
        return get_soup_at_url(cls.url).find('li', class_='first').find('a')
3020
3021
    @classmethod
3022
    def get_comic_info(cls, soup, link):
3023
        """Get information about a particular comics."""
3024
        title = soup.find('meta', property='og:title')['content']
3025
        desc = soup.find('meta', property='og:description')['content']
3026
        date_str = soup.find('time')["datetime"]
3027
        day = string_to_date(date_str, "%Y-%m-%d")
3028
        imgs = soup.find('figure').find_all('img')
3029
        return {
3030
            'title': title,
3031
            'description': desc,
3032
            'day': day.day,
3033
            'month': day.month,
3034
            'year': day.year,
3035
            'img': [i['src'] for i in imgs],
3036
        }
3037
3038
3039
class LittleLifeLines(GenericNavigableComic):
3040
    """Class to retrieve Little Life Lines comics."""
3041
    # Also on https://little-life-lines.tumblr.com
3042
    name = 'life'
3043
    long_name = 'Little Life Lines'
3044
    url = 'http://www.littlelifelines.com'
3045
    get_url_from_link = join_cls_url_to_href
3046
    get_first_comic_link = simulate_first_link
3047
    first_url = 'http://www.littlelifelines.com/comics/well-done'
3048
3049
    @classmethod
3050
    def get_navi_link(cls, last_soup, next_):
3051
        """Get link to next or previous comic."""
3052
        # prev is next / next is prev
3053
        li = last_soup.find('li', class_='prev' if next_ else 'next')
3054
        return li.find('a') if li else None
3055
3056 View Code Duplication
    @classmethod
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
3057
    def get_comic_info(cls, soup, link):
3058
        """Get information about a particular comics."""
3059
        title = soup.find('meta', property='og:title')['content']
3060
        desc = soup.find('meta', property='og:description')['content']
3061
        date_str = soup.find('time', class_='published')['datetime']
3062
        day = string_to_date(date_str, "%Y-%m-%d")
3063
        author = soup.find('a', rel='author').string
3064
        div_content = soup.find('div', class_="body entry-content")
3065
        imgs = div_content.find_all('img')
3066
        imgs = [i for i in imgs if i.get('src') is not None]
3067
        alt = imgs[0]['alt']
3068
        return {
3069
            'title': title,
3070
            'alt': alt,
3071
            'description': desc,
3072
            'author': author,
3073
            'day': day.day,
3074
            'month': day.month,
3075
            'year': day.year,
3076
            'img': [i['src'] for i in imgs],
3077
        }
3078
3079
3080
class GenericWordPressInkblot(GenericNavigableComic):
3081
    """Generic class to retrieve comics using WordPress with Inkblot."""
3082
    get_navi_link = get_link_rel_next
3083
3084
    @classmethod
3085
    def get_first_comic_link(cls):
3086
        """Get link to first comics."""
3087
        return get_soup_at_url(cls.url).find('a', class_='webcomic-link webcomic1-link first-webcomic-link first-webcomic1-link')
3088
3089
    @classmethod
3090
    def get_comic_info(cls, soup, link):
3091
        """Get information about a particular comics."""
3092
        title = soup.find('meta', property='og:title')['content']
3093
        imgs = soup.find('div', class_='webcomic-image').find_all('img')
3094
        date_str = soup.find('meta', property='article:published_time')['content'][:10]
3095
        day = string_to_date(date_str, "%Y-%m-%d")
3096
        return {
3097
            'title': title,
3098
            'day': day.day,
3099
            'month': day.month,
3100
            'year': day.year,
3101
            'img': [i['src'] for i in imgs],
3102
        }
3103
3104
3105
class EverythingsStupid(GenericWordPressInkblot):
3106
    """Class to retrieve Everything's stupid Comics."""
3107
    # Also on http://tapastic.com/series/EverythingsStupid
3108
    # Also on http://www.webtoons.com/en/challenge/everythings-stupid/list?title_no=14591
3109
    # Also on http://everythingsstupidcomics.tumblr.com
3110
    name = 'stupid'
3111
    long_name = "Everything's Stupid"
3112
    url = 'http://everythingsstupid.net'
3113
3114
3115
class TheIsmComics(GenericDeletedComic, GenericWordPressInkblot):
3116
    """Class to retrieve The Ism Comics."""
3117
    # Also on https://tapastic.com/series/TheIsm (?)
3118
    name = 'theism'
3119
    long_name = "The Ism"
3120
    url = 'http://www.theism-comics.com'
3121
3122
3123
class WoodenPlankStudios(GenericWordPressInkblot):
3124
    """Class to retrieve Wooden Plank Studios comics."""
3125
    name = 'woodenplank'
3126
    long_name = 'Wooden Plank Studios'
3127
    url = 'http://woodenplankstudios.com'
3128
3129
3130
class ElectricBunnyComic(GenericNavigableComic):
3131
    """Class to retrieve Electric Bunny Comics."""
3132
    # Also on http://electricbunnycomics.tumblr.com
3133
    name = 'bunny'
3134
    long_name = 'Electric Bunny Comic'
3135
    url = 'http://www.electricbunnycomics.com/View/Comic/153/Welcome+to+Hell'
3136
    get_url_from_link = join_cls_url_to_href
3137
3138
    @classmethod
3139
    def get_first_comic_link(cls):
3140
        """Get link to first comics."""
3141
        return get_soup_at_url(cls.url).find('img', alt='First').parent
3142
3143
    @classmethod
3144
    def get_navi_link(cls, last_soup, next_):
3145
        """Get link to next or previous comic."""
3146
        img = last_soup.find('img', alt='Next' if next_ else 'Back')
3147
        return img.parent if img else None
3148
3149
    @classmethod
3150
    def get_comic_info(cls, soup, link):
3151
        """Get information about a particular comics."""
3152
        title = soup.find('meta', property='og:title')['content']
3153
        imgs = soup.find_all('meta', property='og:image')
3154
        return {
3155
            'title': title,
3156
            'img': [i['content'] for i in imgs],
3157
        }
3158
3159
3160
class SheldonComics(GenericNavigableComic):
3161
    """Class to retrieve Sheldon comics."""
3162
    # Also on http://www.gocomics.com/sheldon
3163
    name = 'sheldon'
3164
    long_name = 'Sheldon Comics'
3165
    url = 'http://www.sheldoncomics.com'
3166
3167
    @classmethod
3168
    def get_first_comic_link(cls):
3169
        """Get link to first comics."""
3170
        return get_soup_at_url(cls.url).find("a", id="nav-first")
3171
3172
    @classmethod
3173
    def get_navi_link(cls, last_soup, next_):
3174
        """Get link to next or previous comic."""
3175
        for link in last_soup.find_all("a", id="nav-next" if next_ else "nav-prev"):
3176
            if link['href'] != 'http://www.sheldoncomics.com':
3177
                return link
3178
        return None
3179
3180
    @classmethod
3181
    def get_comic_info(cls, soup, link):
3182
        """Get information about a particular comics."""
3183
        imgs = soup.find("div", id="comic-foot").find_all("img")
3184
        assert all(i['alt'] == i['title'] for i in imgs)
3185
        assert len(imgs) == 1, imgs
3186
        title = imgs[0]['title']
3187
        return {
3188
            'title': title,
3189
            'img': [i['src'] for i in imgs],
3190
        }
3191
3192
3193
class ManVersusManatee(GenericNavigableComic):
3194
    """Class to retrieve Man Versus Manatee comics."""
3195
    url = 'http://manvsmanatee.com'
3196
    name = 'manvsmanatee'
3197
    long_name = 'Man Versus Manatee'
3198
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
3199
    get_navi_link = get_a_comicnavbase_comicnavnext
3200
3201
    @classmethod
3202
    def get_comic_info(cls, soup, link):
3203
        """Get information about a particular comics."""
3204
        title = soup.find('h2', class_='post-title').string
3205
        imgs = soup.find('div', id='comic').find_all('img')
3206
        date_str = soup.find('span', class_='post-date').string
3207
        day = string_to_date(date_str, "%B %d, %Y")
3208
        return {
3209
            'img': [i['src'] for i in imgs],
3210
            'title': title,
3211
            'month': day.month,
3212
            'year': day.year,
3213
            'day': day.day,
3214
        }
3215
3216
3217
class TheMeerkatguy(GenericNavigableComic):
3218
    """Class to retrieve The Meerkatguy comics."""
3219
    long_name = 'The Meerkatguy'
3220
    url = 'http://www.themeerkatguy.com'
3221
    name = 'meerkatguy'
3222
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
3223
    get_navi_link = get_a_comicnavbase_comicnavnext
3224
3225
    @classmethod
3226
    def get_comic_info(cls, soup, link):
3227
        """Get information about a particular comics."""
3228
        title = soup.find('title').string
3229
        imgs = soup.find_all('meta', property='og:image')
3230
        return {
3231
            'img': [i['content'] for i in imgs],
3232
            'title': title,
3233
        }
3234
3235
3236
class Ubertool(GenericNavigableComic):
3237
    """Class to retrieve Ubertool comics."""
3238
    # Also on https://ubertool.tumblr.com
3239
    # Also on https://tapastic.com/series/ubertool
3240
    name = 'ubertool'
3241
    long_name = 'Ubertool'
3242
    url = 'http://ubertoolcomic.com'
3243
    _categories = ('UBERTOOL', )
3244
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
3245
    get_navi_link = get_a_comicnavbase_comicnavnext
3246
3247
    @classmethod
3248
    def get_comic_info(cls, soup, link):
3249
        """Get information about a particular comics."""
3250
        title = soup.find('h2', class_='post-title').string
3251
        date_str = soup.find('span', class_='post-date').string
3252
        day = string_to_date(date_str, "%B %d, %Y")
3253
        imgs = soup.find('div', id='comic').find_all('img')
3254
        return {
3255
            'img': [i['src'] for i in imgs],
3256
            'title': title,
3257
            'month': day.month,
3258
            'year': day.year,
3259
            'day': day.day,
3260
        }
3261
3262
3263
class EarthExplodes(GenericNavigableComic):
3264
    """Class to retrieve The Earth Explodes comics."""
3265
    name = 'earthexplodes'
3266
    long_name = 'The Earth Explodes'
3267
    url = 'http://www.earthexplodes.com'
3268
    get_url_from_link = join_cls_url_to_href
3269
    get_first_comic_link = simulate_first_link
3270
    first_url = 'http://www.earthexplodes.com/comics/000/'
3271
3272
    @classmethod
3273
    def get_navi_link(cls, last_soup, next_):
3274
        """Get link to next or previous comic."""
3275
        return last_soup.find('a', id='next' if next_ else 'prev')
3276
3277
    @classmethod
3278
    def get_comic_info(cls, soup, link):
3279
        """Get information about a particular comics."""
3280
        title = soup.find('title').string
3281
        imgs = soup.find('div', id='image').find_all('img')
3282
        alt = imgs[0].get('title', '')
3283
        return {
3284
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
3285
            'title': title,
3286
            'alt': alt,
3287
        }
3288
3289
3290
class PomComics(GenericNavigableComic):
3291
    """Class to retrieve PomComics."""
3292
    name = 'pom'
3293
    long_name = 'Pom Comics / Piece of Me'
3294
    url = 'http://www.pomcomic.com'
3295
    get_url_from_link = join_cls_url_to_href
3296
3297
    @classmethod
3298
    def get_first_comic_link(cls):
3299
        """Get link to first comics."""
3300
        return get_soup_at_url(cls.url).find('a', class_='btn-first')
3301
3302
    @classmethod
3303
    def get_navi_link(cls, last_soup, next_):
3304
        """Get link to next or previous comic."""
3305
        return last_soup.find('a', class_='btn-next' if next_ else 'btn-prev')
3306
3307
    @classmethod
3308
    def get_comic_info(cls, soup, link):
3309
        """Get information about a particular comics."""
3310
        title = soup.find('h1').string
3311
        desc = soup.find('meta', property='og:description')['content']
3312
        tags = soup.find('meta', attrs={'name': 'keywords'})['content']
3313
        imgs = soup.find('div', class_='comic').find_all('img')
3314
        return {
3315
            'title': title,
3316
            'desc': desc,
3317
            'tags': tags,
3318
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
3319
        }
3320
3321
3322
class CubeDrone(GenericComicNotWorking, GenericNavigableComic):  # Website has changed
3323
    """Class to retrieve Cube Drone comics."""
3324
    name = 'cubedrone'
3325
    long_name = 'Cube Drone'
3326
    url = 'http://cube-drone.com/comics'
3327
    get_url_from_link = join_cls_url_to_href
3328
3329
    @classmethod
3330
    def get_first_comic_link(cls):
3331
        """Get link to first comics."""
3332
        return get_soup_at_url(cls.url).find('span', class_='glyphicon glyphicon-backward').parent
3333
3334
    @classmethod
3335
    def get_navi_link(cls, last_soup, next_):
3336
        """Get link to next or previous comic."""
3337
        class_ = 'glyphicon glyphicon-chevron-' + ('right' if next_ else 'left')
3338
        return last_soup.find('span', class_=class_).parent
3339
3340
    @classmethod
3341
    def get_comic_info(cls, soup, link):
3342
        """Get information about a particular comics."""
3343
        title = soup.find('meta', attrs={'name': 'twitter:title'})['content']
3344
        url2 = soup.find('meta', attrs={'name': 'twitter:url'})['content']
3345
        # date_str = soup.find('h2', class_='comic_title').find('small').string
3346
        # day = string_to_date(date_str, "%B %d, %Y, %I:%M %p")
3347
        imgs = soup.find_all('img', class_='comic img-responsive')
3348
        title2 = imgs[0]['title']
3349
        alt = imgs[0]['alt']
3350
        return {
3351
            'url2': url2,
3352
            'title': title,
3353
            'title2': title2,
3354
            'alt': alt,
3355
            'img': [i['src'] for i in imgs],
3356
        }
3357
3358
3359
class MakeItStoopid(GenericDeletedComic, GenericNavigableComic):
3360
    """Class to retrieve Make It Stoopid Comics."""
3361
    name = 'stoopid'
3362
    long_name = 'Make it stoopid'
3363
    url = 'http://makeitstoopid.com/comic.php'
3364
3365
    @classmethod
3366
    def get_nav(cls, soup):
3367
        """Get the navigation elements from soup object."""
3368
        cnav = soup.find_all(class_='cnav')
3369
        nav1, nav2 = cnav[:5], cnav[5:]
3370
        assert nav1 == nav2
3371
        # begin, prev, archive, next_, end = nav1
3372
        return [None if i.get('href') is None else i for i in nav1]
3373
3374
    @classmethod
3375
    def get_first_comic_link(cls):
3376
        """Get link to first comics."""
3377
        return cls.get_nav(get_soup_at_url(cls.url))[0]
3378
3379
    @classmethod
3380
    def get_navi_link(cls, last_soup, next_):
3381
        """Get link to next or previous comic."""
3382
        return cls.get_nav(last_soup)[3 if next_ else 1]
3383
3384
    @classmethod
3385
    def get_comic_info(cls, soup, link):
3386
        """Get information about a particular comics."""
3387
        title = link['title']
3388
        imgs = soup.find_all('img', id='comicimg')
3389
        return {
3390
            'title': title,
3391
            'img': [i['src'] for i in imgs],
3392
        }
3393
3394
3395
class OffTheLeashDog(GenericNavigableComic):
3396
    """Class to retrieve Off The Leash Dog comics."""
3397
    # Also on http://rupertfawcettsdoggyblog.tumblr.com
3398
    # Also on http://www.rupertfawcettcartoons.com
3399
    name = 'offtheleash'
3400
    long_name = 'Off The Leash Dog'
3401
    url = 'http://offtheleashdogcartoons.com'
3402
    _categories = ('FAWCETT', )
3403
    get_navi_link = get_a_rel_next
3404
    get_first_comic_link = simulate_first_link
3405
    first_url = 'http://offtheleashdogcartoons.com/uncategorized/can-i-help-you/'
3406
3407
    @classmethod
3408
    def get_comic_info(cls, soup, link):
3409
        """Get information about a particular comics."""
3410
        title = soup.find("h1", class_="entry-title").string
3411
        imgs = soup.find('div', class_='entry-content').find_all('img')
3412
        return {
3413
            'title': title,
3414
            'img': [i['src'] for i in imgs],
3415
        }
3416
3417
3418 View Code Duplication
class MarketoonistComics(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
3419
    """Class to retrieve Marketoonist Comics."""
3420
    name = 'marketoonist'
3421
    long_name = 'Marketoonist'
3422
    url = 'https://marketoonist.com/cartoons'
3423
    get_first_comic_link = simulate_first_link
3424
    get_navi_link = get_link_rel_next
3425
    first_url = 'https://marketoonist.com/2002/10/the-8-types-of-brand-managers-2.html'
3426
3427
    @classmethod
3428
    def get_comic_info(cls, soup, link):
3429
        """Get information about a particular comics."""
3430
        imgs = soup.find_all('meta', property='og:image')
3431
        date_str = soup.find('meta', property='article:published_time')['content'][:10]
3432
        day = string_to_date(date_str, "%Y-%m-%d")
3433
        title = soup.find('meta', property='og:title')['content']
3434
        return {
3435
            'img': [i['content'] for i in imgs],
3436
            'day': day.day,
3437
            'month': day.month,
3438
            'year': day.year,
3439
            'title': title,
3440
        }
3441
3442
3443
class ConsoliaComics(GenericNavigableComic):
3444
    """Class to retrieve Consolia comics."""
3445
    name = 'consolia'
3446
    long_name = 'consolia'
3447
    url = 'https://consolia-comic.com'
3448
    get_url_from_link = join_cls_url_to_href
3449
3450
    @classmethod
3451
    def get_first_comic_link(cls):
3452
        """Get link to first comics."""
3453
        return get_soup_at_url(cls.url).find('a', class_='first')
3454
3455
    @classmethod
3456
    def get_navi_link(cls, last_soup, next_):
3457
        """Get link to next or previous comic."""
3458
        return last_soup.find('a', class_='next' if next_ else 'prev')
3459
3460
    @classmethod
3461
    def get_comic_info(cls, soup, link):
3462
        """Get information about a particular comics."""
3463
        title = soup.find('meta', property='og:title')['content']
3464
        date_str = soup.find('time')["datetime"]
3465
        day = string_to_date(date_str, "%Y-%m-%d")
3466
        imgs = soup.find_all('meta', property='og:image')
3467
        return {
3468
            'title': title,
3469
            'img': [i['content'] for i in imgs],
3470 View Code Duplication
            'day': day.day,
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
3471
            'month': day.month,
3472
            'year': day.year,
3473
        }
3474
3475
3476
class GenericBlogspotComic(GenericNavigableComic):
3477
    """Generic class to retrieve comics from Blogspot."""
3478
    get_first_comic_link = simulate_first_link
3479
    first_url = NotImplemented
3480
    _categories = ('BLOGSPOT', )
3481
3482
    @classmethod
3483
    def get_navi_link(cls, last_soup, next_):
3484
        """Get link to next or previous comic."""
3485
        return last_soup.find('a', id='Blog1_blog-pager-newer-link' if next_ else 'Blog1_blog-pager-older-link')
3486
3487
3488
class TuMourrasMoinsBete(GenericBlogspotComic):
3489
    """Class to retrieve Tu Mourras Moins Bete comics."""
3490
    name = 'mourrasmoinsbete'
3491
    long_name = 'Tu Mourras Moins Bete'
3492
    url = 'http://tumourrasmoinsbete.blogspot.fr'
3493
    _categories = ('FRANCAIS', )
3494
    first_url = 'http://tumourrasmoinsbete.blogspot.fr/2008/06/essai.html'
3495
3496
    @classmethod
3497
    def get_comic_info(cls, soup, link):
3498
        """Get information about a particular comics."""
3499
        title = soup.find('title').string
3500
        imgs = soup.find('div', itemprop='description articleBody').find_all('img')
3501
        author = soup.find('span', itemprop='author').string
3502
        return {
3503
            'img': [i['src'] for i in imgs],
3504
            'author': author,
3505
            'title': title,
3506
        }
3507
3508
3509
class Octopuns(GenericBlogspotComic):
3510
    """Class to retrieve Octopuns comics."""
3511
    # Also on http://octopuns.tumblr.com
3512
    name = 'octopuns'
3513
    long_name = 'Octopuns'
3514
    url = 'http://www.octopuns.net'  # or http://octopuns.blogspot.fr/
3515
    first_url = 'http://octopuns.blogspot.com/2010/12/17122010-always-read-label.html'
3516
3517
    @classmethod
3518
    def get_comic_info(cls, soup, link):
3519
        """Get information about a particular comics."""
3520
        title = soup.find('h3', class_='post-title entry-title').string
3521
        date_str = soup.find('h2', class_='date-header').string
3522
        day = string_to_date(date_str, "%A, %B %d, %Y")
3523
        imgs = soup.find_all('link', rel='image_src')
3524
        return {
3525
            'img': [i['href'] for i in imgs],
3526
            'title': title,
3527
            'day': day.day,
3528
            'month': day.month,
3529
            'year': day.year,
3530
        }
3531
3532
3533
class GeekAndPoke(GenericNavigableComic):
3534
    """Class to retrieve Geek And Poke comics."""
3535
    name = 'geek'
3536
    long_name = 'Geek And Poke'
3537
    url = 'http://geek-and-poke.com'
3538
    get_url_from_link = join_cls_url_to_href
3539
    get_first_comic_link = simulate_first_link
3540
    first_url = 'http://geek-and-poke.com/geekandpoke/2006/8/27/a-new-place-for-a-not-so-old-blog.html'
3541
3542
    @classmethod
3543
    def get_navi_link(cls, last_soup, next_):
3544
        """Get link to next or previous comic."""
3545
        return last_soup.find('a', class_='prev-item' if next_ else 'next-item')
3546
3547
    @classmethod
3548
    def get_comic_info(cls, soup, link):
3549
        """Get information about a particular comics."""
3550
        title = soup.find('meta', property='og:title')['content']
3551
        desc = soup.find('meta', property='og:description')
3552
        desc_str = "" if desc is None else desc['content']
3553
        date_str = soup.find('time', class_='published')['datetime']
3554
        day = string_to_date(date_str, "%Y-%m-%d")
3555 View Code Duplication
        author = soup.find('a', rel='author').string
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
3556
        div_content = (soup.find('div', class_="body entry-content") or
3557
                       soup.find('div', class_="special-content"))
3558
        imgs = div_content.find_all('img')
3559
        imgs = [i for i in imgs if i.get('src') is not None]
3560
        assert all('title' not in i or i['alt'] == i['title'] for i in imgs)
3561
        alt = imgs[0].get('alt', "") if imgs else []
3562
        return {
3563
            'title': title,
3564
            'alt': alt,
3565
            'description': desc_str,
3566
            'author': author,
3567
            'day': day.day,
3568
            'month': day.month,
3569
            'year': day.year,
3570
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
3571
        }
3572
3573
3574
class GloryOwlComix(GenericBlogspotComic):
3575
    """Class to retrieve Glory Owl comics."""
3576
    name = 'gloryowl'
3577
    long_name = 'Glory Owl'
3578
    url = 'http://gloryowlcomix.blogspot.fr'
3579
    _categories = ('NSFW', 'FRANCAIS')
3580
    first_url = 'http://gloryowlcomix.blogspot.fr/2013/02/1_7.html'
3581
3582
    @classmethod
3583
    def get_comic_info(cls, soup, link):
3584
        """Get information about a particular comics."""
3585
        title = soup.find('title').string
3586
        imgs = soup.find_all('link', rel='image_src')
3587
        author = soup.find('a', rel='author').string
3588
        return {
3589
            'img': [i['href'] for i in imgs],
3590
            'author': author,
3591
            'title': title,
3592
        }
3593
3594
3595
class AtRandomComics(GenericNavigableComic):
3596
    """Class to retrieve At Random Comics."""
3597
    name = 'atrandom'
3598
    long_name = 'At Random Comics'
3599
    url = 'http://www.atrandomcomics.com'
3600
    get_url_from_link = join_cls_url_to_href
3601
    get_first_comic_link = simulate_first_link
3602
    first_url = 'http://www.atrandomcomics.com/at-random-comics-home/2015/5/5/can-of-worms'
3603
3604
    @classmethod
3605
    def get_navi_link(cls, last_soup, next_):
3606
        """Get link to next or previous comic."""
3607
        return last_soup.find('a', id='prevLink' if next_ else 'nextLink')
3608
3609
    @classmethod
3610
    def get_comic_info(cls, soup, link):
3611
        """Get information about a particular comics."""
3612
        title = soup.find('meta', property='og:title')['content']
3613
        desc = soup.find('meta', property='og:description')['content']
3614
        date_str = soup.find('time', itemprop='datePublished')["datetime"]
3615
        day = string_to_date(date_str, "%Y-%m-%d")
3616
        author = soup.find('a', rel='author').string
3617
        imgs = soup.find_all('meta', property='og:image')
3618
        return {
3619
            'title': title,
3620
            'img': [i['content'] for i in imgs],
3621
            'month': day.month,
3622
            'year': day.year,
3623
            'day': day.day,
3624
            'author': author,
3625
            'description': desc,
3626
        }
3627
3628
3629
class GenericTumblrV1(GenericComic):
3630
    """Generic class to retrieve comics from Tumblr using the V1 API."""
3631
    _categories = ('TUMBLR', )
3632
3633
    @classmethod
3634
    def get_next_comic(cls, last_comic):
3635
        """Generic implementation of get_next_comic for Tumblr comics."""
3636
        for p in cls.get_posts(last_comic):
3637
            comic = cls.get_comic_info(p)
3638
            if comic is not None:
3639
                yield comic
3640
3641
    @classmethod
3642
    def check_url(cls, url):
3643
        if not url.startswith(cls.url):
3644
            print("url '%s' does not start with '%s'" % (url, cls.url))
3645
        return url
3646
3647
    @classmethod
3648
    def get_url_from_post(cls, post):
3649
        return cls.check_url(post['url'])
3650
3651
    @classmethod
3652
    def get_api_url(cls):
3653
        return urljoin_wrapper(cls.url, '/api/read/')
3654
3655
    @classmethod
3656
    def get_api_url_for_id(cls, tumblr_id):
3657
        return cls.get_api_url() + '?id=%d' % (tumblr_id)
3658
3659
    @classmethod
3660
    def get_comic_info(cls, post):
3661
        """Get information about a particular comics."""
3662
        type_ = post['type']
3663
        if type_ != 'photo':
3664
            return None
3665
        tumblr_id = int(post['id'])
3666
        api_url = cls.get_api_url_for_id(tumblr_id)
3667
        day = datetime.datetime.fromtimestamp(int(post['unix-timestamp'])).date()
3668
        caption = post.find('photo-caption')
3669
        title = caption.string if caption else ""
3670
        tags = ' '.join(t.string for t in post.find_all('tag'))
3671
        # Photos may appear in 'photo' tags and/or straight in the post
3672
        photo_tags = post.find_all('photo')
3673
        if not photo_tags:
3674
            photo_tags = [post]
3675
        # Images are in multiple resolutions - taking the first one
3676
        imgs = [photo.find('photo-url') for photo in photo_tags]
3677
        return {
3678
            'url': cls.get_url_from_post(post),
3679
            'url2': post['url-with-slug'],
3680
            'day': day.day,
3681
            'month': day.month,
3682
            'year': day.year,
3683
            'title': title,
3684
            'tags': tags,
3685
            'img': [i.string for i in imgs],
3686
            'tumblr-id': tumblr_id,
3687
            'api_url': api_url,
3688
        }
3689
3690
    @classmethod
3691
    def get_posts(cls, last_comic, nb_post_per_call=10):
3692
        """Get posts using API. nb_post_per_call is max 50.
3693
3694
        Posts are retrieved from newer to older as per the tumblr v1 api
3695
        but are returned in chronological order."""
3696
        waiting_for_id = last_comic['tumblr-id'] if last_comic else None
3697
        posts_acc = []
3698
        if last_comic is not None:
3699
            # cls.check_url(last_comic['url'])
3700
            cls.check_url(last_comic['api_url'])
3701
            # Sometimes, tumblr posts are deleted. When previous post is deleted, we
3702
            # might end up spending a lot of time looking for something that
3703
            # doesn't exist. Failing early and clearly might be a better option.
3704
            last_api_url = cls.get_api_url_for_id(waiting_for_id)
3705
            try:
3706
                get_soup_at_url(last_api_url)
3707
            except urllib.error.HTTPError:
3708
                try:
3709
                    get_soup_at_url(cls.url)
3710
                except urllib.error.HTTPError:
3711
                    print("Did not find previous post nor main url %s" % cls.url)
3712
                else:
3713
                    print("Did not find previous post %s : it might have been deleted" % last_api_url)
3714
                return reversed(posts_acc)
3715
        api_url = cls.get_api_url()
3716
        posts = get_soup_at_url(api_url).find('posts')
3717
        start, total = int(posts['start']), int(posts['total'])
3718
        assert start == 0
3719
        for starting_num in range(0, total, nb_post_per_call):
3720
            api_url2 = api_url + '?start=%d&num=%d' % (starting_num, nb_post_per_call)
3721
            posts2 = get_soup_at_url(api_url2).find('posts')
3722
            start2, total2 = int(posts2['start']), int(posts2['total'])
3723
            assert starting_num == start2, "%d != %d" % (starting_num, start2)
3724
            # This may happen and should be handled in the future
3725
            assert total == total2, "%d != %d" % (total, total2)
3726
            for p in posts2.find_all('post'):
3727
                tumblr_id = int(p['id'])
3728
                if waiting_for_id and waiting_for_id == tumblr_id:
3729
                    return reversed(posts_acc)
3730
                posts_acc.append(p)
3731
        if waiting_for_id is None:
3732
            return reversed(posts_acc)
3733
        print("Did not find %s : there might be a problem" % waiting_for_id)
3734
        return []
3735
3736
3737
class SaturdayMorningBreakfastCerealTumblr(GenericTumblrV1):
3738
    """Class to retrieve Saturday Morning Breakfast Cereal comics."""
3739
    # Also on http://www.gocomics.com/saturday-morning-breakfast-cereal
3740
    # Also on http://www.smbc-comics.com
3741
    name = 'smbc-tumblr'
3742
    long_name = 'Saturday Morning Breakfast Cereal (from Tumblr)'
3743
    url = 'http://smbc-comics.tumblr.com'
3744
    _categories = ('SMBC', )
3745
3746
3747
class AHammADay(GenericTumblrV1):
3748
    """Class to retrieve class A Hamm A Day comics."""
3749
    name = 'hamm'
3750
    long_name = 'A Hamm A Day'
3751
    url = 'http://www.ahammaday.com'
3752
3753
3754
class IrwinCardozo(GenericTumblrV1):
3755
    """Class to retrieve Irwin Cardozo Comics."""
3756
    name = 'irwinc'
3757
    long_name = 'Irwin Cardozo'
3758
    url = 'http://irwincardozocomics.tumblr.com'
3759
3760
3761
class AccordingToDevin(GenericTumblrV1):
3762
    """Class to retrieve According To Devin comics."""
3763
    name = 'devin'
3764
    long_name = 'According To Devin'
3765
    url = 'http://accordingtodevin.tumblr.com'
3766
3767
3768
class ItsTheTieTumblr(GenericTumblrV1):
3769
    """Class to retrieve It's the tie comics."""
3770
    # Also on http://itsthetie.com
3771
    # Also on https://tapastic.com/series/itsthetie
3772
    name = 'tie-tumblr'
3773
    long_name = "It's the tie (from Tumblr)"
3774
    url = "http://itsthetie.tumblr.com"
3775
    _categories = ('TIE', )
3776
3777
3778
class OctopunsTumblr(GenericTumblrV1):
3779
    """Class to retrieve Octopuns comics."""
3780
    # Also on http://www.octopuns.net
3781
    name = 'octopuns-tumblr'
3782
    long_name = 'Octopuns (from Tumblr)'
3783
    url = 'http://octopuns.tumblr.com'
3784
3785
3786
class PicturesInBoxesTumblr(GenericTumblrV1):
3787
    """Class to retrieve Pictures In Boxes comics."""
3788
    # Also on http://www.picturesinboxes.com
3789
    name = 'picturesinboxes-tumblr'
3790
    long_name = 'Pictures in Boxes (from Tumblr)'
3791
    url = 'https://picturesinboxescomic.tumblr.com'
3792
3793
3794
class TubeyToonsTumblr(GenericTumblrV1):
3795
    """Class to retrieve TubeyToons comics."""
3796
    # Also on http://tapastic.com/series/Tubey-Toons
3797
    # Also on http://tubeytoons.com
3798
    name = 'tubeytoons-tumblr'
3799
    long_name = 'Tubey Toons (from Tumblr)'
3800
    url = 'https://tubeytoons.tumblr.com'
3801
    _categories = ('TUNEYTOONS', )
3802
3803
3804
class UnearthedComicsTumblr(GenericTumblrV1):
3805
    """Class to retrieve Unearthed comics."""
3806
    # Also on http://tapastic.com/series/UnearthedComics
3807
    # Also on http://unearthedcomics.com
3808
    name = 'unearthed-tumblr'
3809
    long_name = 'Unearthed Comics (from Tumblr)'
3810
    url = 'https://unearthedcomics.tumblr.com'
3811
    _categories = ('UNEARTHED', )
3812
3813
3814
class PieComic(GenericTumblrV1):
3815
    """Class to retrieve Pie Comic comics."""
3816
    name = 'pie'
3817
    long_name = 'Pie Comic'
3818
    url = "http://piecomic.tumblr.com"
3819
3820
3821
class MrEthanDiamond(GenericTumblrV1):
3822
    """Class to retrieve Mr Ethan Diamond comics."""
3823
    name = 'diamond'
3824
    long_name = 'Mr Ethan Diamond'
3825
    url = 'http://mrethandiamond.tumblr.com'
3826
3827
3828
class Flocci(GenericTumblrV1):
3829
    """Class to retrieve floccinaucinihilipilification comics."""
3830
    name = 'flocci'
3831
    long_name = 'floccinaucinihilipilification'
3832
    url = "http://floccinaucinihilipilificationa.tumblr.com"
3833
3834
3835
class UpAndOut(GenericTumblrV1):
3836
    """Class to retrieve Up & Out comics."""
3837
    # Also on http://tapastic.com/series/UP-and-OUT
3838
    name = 'upandout'
3839
    long_name = 'Up And Out (from Tumblr)'
3840
    url = 'http://upandoutcomic.tumblr.com'
3841
3842
3843
class Pundemonium(GenericTumblrV1):
3844
    """Class to retrieve Pundemonium comics."""
3845
    name = 'pundemonium'
3846
    long_name = 'Pundemonium'
3847
    url = 'http://monstika.tumblr.com'
3848
3849
3850
class PoorlyDrawnLinesTumblr(GenericTumblrV1):
3851
    """Class to retrieve Poorly Drawn Lines comics."""
3852
    # Also on http://poorlydrawnlines.com
3853
    name = 'poorlydrawn-tumblr'
3854
    long_name = 'Poorly Drawn Lines (from Tumblr)'
3855
    url = 'http://pdlcomics.tumblr.com'
3856
    _categories = ('POORLYDRAWN', )
3857
3858
3859
class PearShapedComics(GenericTumblrV1):
3860
    """Class to retrieve Pear Shaped Comics."""
3861
    name = 'pearshaped'
3862
    long_name = 'Pear-Shaped Comics'
3863
    url = 'http://pearshapedcomics.com'
3864
3865
3866
class PondScumComics(GenericTumblrV1):
3867
    """Class to retrieve Pond Scum Comics."""
3868
    name = 'pond'
3869
    long_name = 'Pond Scum'
3870
    url = 'http://pondscumcomic.tumblr.com'
3871
3872
3873
class MercworksTumblr(GenericTumblrV1):
3874
    """Class to retrieve Mercworks comics."""
3875
    # Also on http://mercworks.net
3876
    name = 'mercworks-tumblr'
3877
    long_name = 'Mercworks (from Tumblr)'
3878
    url = 'http://mercworks.tumblr.com'
3879
3880
3881
class OwlTurdTumblr(GenericTumblrV1):
3882
    """Class to retrieve Owl Turd comics."""
3883
    # Also on http://tapastic.com/series/Owl-Turd-Comix
3884
    name = 'owlturd-tumblr'
3885
    long_name = 'Owl Turd (from Tumblr)'
3886
    url = 'http://owlturd.com'
3887
    _categories = ('OWLTURD', )
3888
3889
3890
class VectorBelly(GenericTumblrV1):
3891
    """Class to retrieve Vector Belly comics."""
3892
    # Also on http://vectorbelly.com
3893
    name = 'vector'
3894
    long_name = 'Vector Belly'
3895
    url = 'http://vectorbelly.tumblr.com'
3896
3897
3898
class GoneIntoRapture(GenericTumblrV1):
3899
    """Class to retrieve Gone Into Rapture comics."""
3900
    # Also on http://goneintorapture.tumblr.com
3901
    # Also on http://tapastic.com/series/Goneintorapture
3902
    name = 'rapture'
3903
    long_name = 'Gone Into Rapture'
3904
    url = 'http://goneintorapture.com'
3905
3906
3907
class TheOatmealTumblr(GenericTumblrV1):
3908
    """Class to retrieve The Oatmeal comics."""
3909
    # Also on http://theoatmeal.com
3910
    name = 'oatmeal-tumblr'
3911
    long_name = 'The Oatmeal (from Tumblr)'
3912
    url = 'http://oatmeal.tumblr.com'
3913
3914
3915
class HeckIfIKnowComicsTumblr(GenericTumblrV1):
3916
    """Class to retrieve Heck If I Know Comics."""
3917
    # Also on http://tapastic.com/series/Regular
3918
    name = 'heck-tumblr'
3919
    long_name = 'Heck if I Know comics (from Tumblr)'
3920
    url = 'http://heckifiknowcomics.com'
3921
3922
3923
class MyJetPack(GenericTumblrV1):
3924
    """Class to retrieve My Jet Pack comics."""
3925
    name = 'jetpack'
3926
    long_name = 'My Jet Pack'
3927
    url = 'http://myjetpack.tumblr.com'
3928
3929
3930
class CheerUpEmoKidTumblr(GenericTumblrV1):
3931
    """Class to retrieve CheerUpEmoKid comics."""
3932
    # Also on http://www.cheerupemokid.com
3933
    # Also on http://tapastic.com/series/CUEK
3934
    name = 'cuek-tumblr'
3935
    long_name = 'Cheer Up Emo Kid (from Tumblr)'
3936
    url = 'https://enzocomics.tumblr.com'
3937
3938
3939
class ForLackOfABetterComic(GenericTumblrV1):
3940
    """Class to retrieve For Lack Of A Better Comics."""
3941
    # Also on http://forlackofabettercomic.com
3942
    name = 'lack'
3943
    long_name = 'For Lack Of A Better Comic'
3944
    url = 'http://forlackofabettercomic.tumblr.com'
3945
3946
3947
class ZenPencilsTumblr(GenericTumblrV1):
3948
    """Class to retrieve ZenPencils comics."""
3949
    # Also on http://zenpencils.com
3950
    # Also on http://www.gocomics.com/zen-pencils
3951
    name = 'zenpencils-tumblr'
3952
    long_name = 'Zen Pencils (from Tumblr)'
3953
    url = 'http://zenpencils.tumblr.com'
3954
    _categories = ('ZENPENCILS', )
3955
3956
3957
class ThreeWordPhraseTumblr(GenericTumblrV1):
3958
    """Class to retrieve Three Word Phrase comics."""
3959
    # Also on http://threewordphrase.com
3960
    name = 'threeword-tumblr'
3961
    long_name = 'Three Word Phrase (from Tumblr)'
3962
    url = 'http://threewordphrase.tumblr.com'
3963
3964
3965
class TimeTrabbleTumblr(GenericTumblrV1):
3966
    """Class to retrieve Time Trabble comics."""
3967
    # Also on http://timetrabble.com
3968
    name = 'timetrabble-tumblr'
3969
    long_name = 'Time Trabble (from Tumblr)'
3970
    url = 'http://timetrabble.tumblr.com'
3971
3972
3973
class SafelyEndangeredTumblr(GenericTumblrV1):
3974
    """Class to retrieve Safely Endangered comics."""
3975
    # Also on http://www.safelyendangered.com
3976
    name = 'endangered-tumblr'
3977
    long_name = 'Safely Endangered (from Tumblr)'
3978
    url = 'http://tumblr.safelyendangered.com'
3979
3980
3981
class MouseBearComedyTumblr(GenericTumblrV1):
3982
    """Class to retrieve Mouse Bear Comedy comics."""
3983
    # Also on http://www.mousebearcomedy.com
3984
    name = 'mousebear-tumblr'
3985
    long_name = 'Mouse Bear Comedy (from Tumblr)'
3986
    url = 'http://mousebearcomedy.tumblr.com'
3987
3988
3989
class BouletCorpTumblr(GenericTumblrV1):
3990
    """Class to retrieve BouletCorp comics."""
3991
    # Also on http://www.bouletcorp.com
3992
    name = 'boulet-tumblr'
3993
    long_name = 'Boulet Corp (from Tumblr)'
3994
    url = 'https://bouletcorp.tumblr.com'
3995
    _categories = ('BOULET', )
3996
3997
3998
class TheAwkwardYetiTumblr(GenericTumblrV1):
3999
    """Class to retrieve The Awkward Yeti comics."""
4000
    # Also on http://www.gocomics.com/the-awkward-yeti
4001
    # Also on http://theawkwardyeti.com
4002
    # Also on https://tapastic.com/series/TheAwkwardYeti
4003
    name = 'yeti-tumblr'
4004
    long_name = 'The Awkward Yeti (from Tumblr)'
4005
    url = 'http://larstheyeti.tumblr.com'
4006
    _categories = ('YETI', )
4007
4008
4009
class NellucNhoj(GenericTumblrV1):
4010
    """Class to retrieve NellucNhoj comics."""
4011
    name = 'nhoj'
4012
    long_name = 'Nelluc Nhoj'
4013
    url = 'http://nellucnhoj.com'
4014
4015
4016
class DownTheUpwardSpiralTumblr(GenericTumblrV1):
4017
    """Class to retrieve Down The Upward Spiral comics."""
4018
    # Also on http://www.downtheupwardspiral.com
4019
    # Also on https://tapastic.com/series/Down-the-Upward-Spiral
4020
    name = 'spiral-tumblr'
4021
    long_name = 'Down the Upward Spiral (from Tumblr)'
4022
    url = 'http://downtheupwardspiral.tumblr.com'
4023
4024
4025
class AsPerUsualTumblr(GenericTumblrV1):
4026
    """Class to retrieve As Per Usual comics."""
4027
    # Also on https://tapastic.com/series/AsPerUsual
4028
    name = 'usual-tumblr'
4029
    long_name = 'As Per Usual (from Tumblr)'
4030
    url = 'http://as-per-usual.tumblr.com'
4031
    categories = ('DAMILEE', )
4032
4033
4034
class HotComicsForCoolPeopleTumblr(GenericTumblrV1):
4035
    """Class to retrieve Hot Comics For Cool People."""
4036
    # Also on https://tapastic.com/series/Hot-Comics-For-Cool-People
4037
    # Also on http://hotcomics.biz (links to tumblr)
4038
    # Also on http://hcfcp.com (links to tumblr)
4039
    name = 'hotcomics-tumblr'
4040
    long_name = 'Hot Comics For Cool People (from Tumblr)'
4041
    url = 'http://hotcomicsforcoolpeople.tumblr.com'
4042
    categories = ('DAMILEE', )
4043
4044
4045
class OneOneOneOneComicTumblr(GenericTumblrV1):
4046
    """Class to retrieve 1111 Comics."""
4047
    # Also on http://www.1111comics.me
4048
    # Also on https://tapastic.com/series/1111-Comics
4049
    name = '1111-tumblr'
4050
    long_name = '1111 Comics (from Tumblr)'
4051
    url = 'http://comics1111.tumblr.com'
4052
    _categories = ('ONEONEONEONE', )
4053
4054
4055
class JhallComicsTumblr(GenericTumblrV1):
4056
    """Class to retrieve Jhall Comics."""
4057
    # Also on http://jhallcomics.com
4058
    name = 'jhall-tumblr'
4059
    long_name = 'Jhall Comics (from Tumblr)'
4060
    url = 'http://jhallcomics.tumblr.com'
4061
4062
4063
class BerkeleyMewsTumblr(GenericTumblrV1):
4064
    """Class to retrieve Berkeley Mews comics."""
4065
    # Also on http://www.gocomics.com/berkeley-mews
4066
    # Also on http://www.berkeleymews.com
4067
    name = 'berkeley-tumblr'
4068
    long_name = 'Berkeley Mews (from Tumblr)'
4069
    url = 'http://mews.tumblr.com'
4070
    _categories = ('BERKELEY', )
4071
4072
4073
class JoanCornellaTumblr(GenericTumblrV1):
4074
    """Class to retrieve Joan Cornella comics."""
4075
    # Also on http://joancornella.net
4076
    name = 'cornella-tumblr'
4077
    long_name = 'Joan Cornella (from Tumblr)'
4078
    url = 'http://cornellajoan.tumblr.com'
4079
4080
4081
class RespawnComicTumblr(GenericTumblrV1):
4082
    """Class to retrieve Respawn Comic."""
4083
    # Also on http://respawncomic.com
4084
    name = 'respawn-tumblr'
4085
    long_name = 'Respawn Comic (from Tumblr)'
4086
    url = 'https://respawncomic.tumblr.com'
4087
4088
4089
class ChrisHallbeckTumblr(GenericTumblrV1):
4090
    """Class to retrieve Chris Hallbeck comics."""
4091
    # Also on https://tapastic.com/ChrisHallbeck
4092
    # Also on http://maximumble.com
4093
    # Also on http://minimumble.com
4094
    # Also on http://thebookofbiff.com
4095
    name = 'hallbeck-tumblr'
4096
    long_name = 'Chris Hallback (from Tumblr)'
4097
    url = 'https://chrishallbeck.tumblr.com'
4098
    _categories = ('HALLBACK', )
4099
4100
4101
class ComicNuggets(GenericTumblrV1):
4102
    """Class to retrieve Comic Nuggets."""
4103
    name = 'nuggets'
4104
    long_name = 'Comic Nuggets'
4105
    url = 'http://comicnuggets.com'
4106
4107
4108
class PigeonGazetteTumblr(GenericTumblrV1):
4109
    """Class to retrieve The Pigeon Gazette comics."""
4110
    # Also on https://tapastic.com/series/The-Pigeon-Gazette
4111
    name = 'pigeon-tumblr'
4112
    long_name = 'The Pigeon Gazette (from Tumblr)'
4113
    url = 'http://thepigeongazette.tumblr.com'
4114
4115
4116
class CancerOwl(GenericTumblrV1):
4117
    """Class to retrieve Cancer Owl comics."""
4118
    # Also on http://cancerowl.com
4119
    name = 'cancerowl-tumblr'
4120
    long_name = 'Cancer Owl (from Tumblr)'
4121
    url = 'http://cancerowl.tumblr.com'
4122
4123
4124
class FowlLanguageTumblr(GenericTumblrV1):
4125
    """Class to retrieve Fowl Language comics."""
4126
    # Also on http://www.fowllanguagecomics.com
4127
    # Also on http://tapastic.com/series/Fowl-Language-Comics
4128
    # Also on http://www.gocomics.com/fowl-language
4129
    name = 'fowllanguage-tumblr'
4130
    long_name = 'Fowl Language Comics (from Tumblr)'
4131
    url = 'http://fowllanguagecomics.tumblr.com'
4132
    _categories = ('FOWLLANGUAGE', )
4133
4134
4135
class TheOdd1sOutTumblr(GenericTumblrV1):
4136
    """Class to retrieve The Odd 1s Out comics."""
4137
    # Also on http://theodd1sout.com
4138
    # Also on https://tapastic.com/series/Theodd1sout
4139
    name = 'theodd-tumblr'
4140
    long_name = 'The Odd 1s Out (from Tumblr)'
4141
    url = 'http://theodd1sout.tumblr.com'
4142
4143
4144
class TheUnderfoldTumblr(GenericTumblrV1):
4145
    """Class to retrieve The Underfold comics."""
4146
    # Also on http://theunderfold.com
4147
    name = 'underfold-tumblr'
4148
    long_name = 'The Underfold (from Tumblr)'
4149
    url = 'http://theunderfold.tumblr.com'
4150
4151
4152
class LolNeinTumblr(GenericTumblrV1):
4153
    """Class to retrieve Lol Nein comics."""
4154
    # Also on http://lolnein.com
4155
    name = 'lolnein-tumblr'
4156
    long_name = 'Lol Nein (from Tumblr)'
4157
    url = 'http://lolneincom.tumblr.com'
4158
4159
4160
class FatAwesomeComicsTumblr(GenericTumblrV1):
4161
    """Class to retrieve Fat Awesome Comics."""
4162
    # Also on http://fatawesome.com/comics
4163
    name = 'fatawesome-tumblr'
4164
    long_name = 'Fat Awesome (from Tumblr)'
4165
    url = 'http://fatawesomecomedy.tumblr.com'
4166
4167
4168
class TheWorldIsFlatTumblr(GenericTumblrV1):
4169
    """Class to retrieve The World Is Flat Comics."""
4170
    # Also on https://tapastic.com/series/The-World-is-Flat
4171
    name = 'flatworld-tumblr'
4172
    long_name = 'The World Is Flat (from Tumblr)'
4173
    url = 'http://theworldisflatcomics.com'
4174
4175
4176
class DorrisMc(GenericTumblrV1):
4177
    """Class to retrieve Dorris Mc Comics"""
4178
    # Also on http://www.gocomics.com/dorris-mccomics
4179
    name = 'dorrismc'
4180
    long_name = 'Dorris Mc'
4181
    url = 'http://dorrismccomics.com'
4182
4183
4184
class LeleozTumblr(GenericDeletedComic, GenericTumblrV1):
4185
    """Class to retrieve Leleoz comics."""
4186
    # Also on https://tapastic.com/series/Leleoz
4187
    name = 'leleoz-tumblr'
4188
    long_name = 'Leleoz (from Tumblr)'
4189
    url = 'http://leleozcomics.tumblr.com'
4190
4191
4192
class MoonBeardTumblr(GenericTumblrV1):
4193
    """Class to retrieve MoonBeard comics."""
4194
    # Also on http://moonbeard.com
4195
    # Also on http://www.webtoons.com/en/comedy/moon-beard/list?title_no=471
4196
    name = 'moonbeard-tumblr'
4197
    long_name = 'Moon Beard (from Tumblr)'
4198
    url = 'http://squireseses.tumblr.com'
4199
    _categories = ('MOONBEARD', )
4200
4201
4202
class AComik(GenericTumblrV1):
4203
    """Class to retrieve A Comik"""
4204
    name = 'comik'
4205
    long_name = 'A Comik'
4206
    url = 'http://acomik.com'
4207
4208
4209
class ClassicRandy(GenericTumblrV1):
4210
    """Class to retrieve Classic Randy comics."""
4211
    name = 'randy'
4212
    long_name = 'Classic Randy'
4213
    url = 'http://classicrandy.tumblr.com'
4214
4215
4216
class DagssonTumblr(GenericTumblrV1):
4217
    """Class to retrieve Dagsson comics."""
4218
    # Also on http://www.dagsson.com
4219
    name = 'dagsson-tumblr'
4220
    long_name = 'Dagsson Hugleikur (from Tumblr)'
4221
    url = 'https://hugleikurdagsson.tumblr.com'
4222
4223
4224
class LinsEditionsTumblr(GenericTumblrV1):
4225
    """Class to retrieve L.I.N.S. Editions comics."""
4226
    # Also on https://linsedition.com
4227
    # Now on http://warandpeas.tumblr.com
4228
    name = 'lins-tumblr'
4229
    long_name = 'L.I.N.S. Editions (from Tumblr)'
4230
    url = 'https://linscomics.tumblr.com'
4231
    _categories = ('LINS', )
4232
4233
4234
class WarAndPeasTumblr(GenericTumblrV1):
4235
    """Class to retrieve War And Peas comics."""
4236
    # Was on https://linscomics.tumblr.com
4237
    name = 'warandpeas-tumblr'
4238
    long_name = 'War And Peas (from Tumblr)'
4239
    url = 'http://warandpeas.tumblr.com'
4240
    _categories = ('WARANDPEAS', )
4241
4242
4243
class OrigamiHotDish(GenericTumblrV1):
4244
    """Class to retrieve Origami Hot Dish comics."""
4245
    name = 'origamihotdish'
4246
    long_name = 'Origami Hot Dish'
4247
    url = 'http://origamihotdish.com'
4248
4249
4250
class HitAndMissComicsTumblr(GenericTumblrV1):
4251
    """Class to retrieve Hit and Miss Comics."""
4252
    name = 'hitandmiss'
4253
    long_name = 'Hit and Miss Comics'
4254
    url = 'https://hitandmisscomics.tumblr.com'
4255
4256
4257
class HMBlanc(GenericTumblrV1):
4258
    """Class to retrieve HM Blanc comics."""
4259
    name = 'hmblanc'
4260
    long_name = 'HM Blanc'
4261
    url = 'http://hmblanc.tumblr.com'
4262
4263
4264
class TalesOfAbsurdityTumblr(GenericTumblrV1):
4265
    """Class to retrieve Tales Of Absurdity comics."""
4266
    # Also on http://talesofabsurdity.com
4267
    # Also on http://tapastic.com/series/Tales-Of-Absurdity
4268
    name = 'absurdity-tumblr'
4269
    long_name = 'Tales of Absurdity (from Tumblr)'
4270
    url = 'http://talesofabsurdity.tumblr.com'
4271
    _categories = ('ABSURDITY', )
4272
4273
4274
class RobbieAndBobby(GenericTumblrV1):
4275
    """Class to retrieve Robbie And Bobby comics."""
4276
    # Also on http://robbieandbobby.com
4277
    name = 'robbie-tumblr'
4278
    long_name = 'Robbie And Bobby (from Tumblr)'
4279
    url = 'http://robbieandbobby.tumblr.com'
4280
4281
4282
class ElectricBunnyComicTumblr(GenericTumblrV1):
4283
    """Class to retrieve Electric Bunny Comics."""
4284
    # Also on http://www.electricbunnycomics.com/View/Comic/153/Welcome+to+Hell
4285
    name = 'bunny-tumblr'
4286
    long_name = 'Electric Bunny Comic (from Tumblr)'
4287
    url = 'http://electricbunnycomics.tumblr.com'
4288
4289
4290
class Hoomph(GenericTumblrV1):
4291
    """Class to retrieve Hoomph comics."""
4292
    name = 'hoomph'
4293
    long_name = 'Hoomph'
4294
    url = 'http://hoom.ph'
4295
4296
4297
class BFGFSTumblr(GenericTumblrV1):
4298
    """Class to retrieve BFGFS comics."""
4299
    # Also on https://tapastic.com/series/BFGFS
4300
    # Also on http://bfgfs.com
4301
    name = 'bfgfs-tumblr'
4302
    long_name = 'BFGFS (from Tumblr)'
4303
    url = 'https://bfgfs.tumblr.com'
4304
4305
4306
class DoodleForFood(GenericTumblrV1):
4307
    """Class to retrieve Doodle For Food comics."""
4308
    # Also on https://tapastic.com/series/Doodle-for-Food
4309
    name = 'doodle'
4310
    long_name = 'Doodle For Food'
4311
    url = 'http://www.doodleforfood.com'
4312
4313
4314
class CassandraCalinTumblr(GenericTumblrV1):
4315
    """Class to retrieve C. Cassandra comics."""
4316
    # Also on http://cassandracalin.com
4317
    # Also on https://tapastic.com/series/C-Cassandra-comics
4318
    name = 'cassandra-tumblr'
4319
    long_name = 'Cassandra Calin (from Tumblr)'
4320
    url = 'http://c-cassandra.tumblr.com'
4321
4322
4323
class DougWasTaken(GenericTumblrV1):
4324
    """Class to retrieve Doug Was Taken comics."""
4325
    name = 'doug'
4326
    long_name = 'Doug Was Taken'
4327
    url = 'https://dougwastaken.tumblr.com'
4328
4329
4330
class MandatoryRollerCoaster(GenericTumblrV1):
4331
    """Class to retrieve Mandatory Roller Coaster comics."""
4332
    name = 'rollercoaster'
4333
    long_name = 'Mandatory Roller Coaster'
4334
    url = 'http://mandatoryrollercoaster.com'
4335
4336
4337
class CEstPasEnRegardantSesPompes(GenericTumblrV1):
4338
    """Class to retrieve C'Est Pas En Regardant Ses Pompes (...)  comics."""
4339
    name = 'cperspqccltt'
4340
    long_name = 'C Est Pas En Regardant Ses Pompes (...)'
4341
    url = 'http://marcoandco.tumblr.com'
4342
4343
4344
class TheGrohlTroll(GenericTumblrV1):
4345
    """Class to retrieve The Grohl Troll comics."""
4346
    name = 'grohltroll'
4347
    long_name = 'The Grohl Troll'
4348
    url = 'http://thegrohltroll.com'
4349
4350
4351
class WebcomicName(GenericTumblrV1):
4352
    """Class to retrieve Webcomic Name comics."""
4353
    name = 'webcomicname'
4354
    long_name = 'Webcomic Name'
4355
    url = 'http://webcomicname.com'
4356
4357
4358
class BooksOfAdam(GenericTumblrV1):
4359
    """Class to retrieve Books of Adam comics."""
4360
    # Also on http://www.booksofadam.com
4361
    name = 'booksofadam'
4362
    long_name = 'Books of Adam'
4363
    url = 'http://booksofadam.tumblr.com'
4364
4365
4366
class HarkAVagrant(GenericTumblrV1):
4367
    """Class to retrieve Hark A Vagrant comics."""
4368
    # Also on http://www.harkavagrant.com
4369
    name = 'hark-tumblr'
4370
    long_name = 'Hark A Vagrant (from Tumblr)'
4371
    url = 'http://beatonna.tumblr.com'
4372
4373
4374
class OurSuperAdventureTumblr(GenericTumblrV1):
4375
    """Class to retrieve Our Super Adventure comics."""
4376
    # Also on https://tapastic.com/series/Our-Super-Adventure
4377
    # Also on http://www.oursuperadventure.com
4378
    # http://sarahgraley.com
4379
    name = 'superadventure-tumblr'
4380
    long_name = 'Our Super Adventure (from Tumblr)'
4381
    url = 'http://sarahssketchbook.tumblr.com'
4382
4383
4384
class JakeLikesOnions(GenericTumblrV1):
4385
    """Class to retrieve Jake Likes Onions comics."""
4386
    name = 'jake'
4387
    long_name = 'Jake Likes Onions'
4388
    url = 'http://jakelikesonions.com'
4389
4390
4391
class InYourFaceCakeTumblr(GenericTumblrV1):
4392
    """Class to retrieve In Your Face Cake comics."""
4393
    # Also on https://tapas.io/series/In-Your-Face-Cake
4394
    name = 'inyourfacecake-tumblr'
4395
    long_name = 'In Your Face Cake (from Tumblr)'
4396
    url = 'https://in-your-face-cake.tumblr.com'
4397
    _categories = ('INYOURFACECAKE', )
4398
4399
4400
class Robospunk(GenericTumblrV1):
4401
    """Class to retrieve Robospunk comics."""
4402
    name = 'robospunk'
4403
    long_name = 'Robospunk'
4404
    url = 'http://robospunk.com'
4405
4406
4407
class BananaTwinky(GenericTumblrV1):
4408
    """Class to retrieve Banana Twinky comics."""
4409
    name = 'banana'
4410
    long_name = 'Banana Twinky'
4411
    url = 'https://bananatwinky.tumblr.com'
4412
4413
4414
class YesterdaysPopcornTumblr(GenericTumblrV1):
4415
    """Class to retrieve Yesterday's Popcorn comics."""
4416
    # Also on http://www.yesterdayspopcorn.com
4417
    # Also on https://tapastic.com/series/Yesterdays-Popcorn
4418
    name = 'popcorn-tumblr'
4419
    long_name = 'Yesterday\'s Popcorn (from Tumblr)'
4420
    url = 'http://yesterdayspopcorn.tumblr.com'
4421
4422
4423
class TwistedDoodles(GenericTumblrV1):
4424
    """Class to retrieve Twisted Doodles comics."""
4425
    name = 'twisted'
4426
    long_name = 'Twisted Doodles'
4427
    url = 'http://www.twisteddoodles.com'
4428
4429
4430
class UbertoolTumblr(GenericTumblrV1):
4431
    """Class to retrieve Ubertool comics."""
4432
    # Also on http://ubertoolcomic.com
4433
    # Also on https://tapastic.com/series/ubertool
4434
    name = 'ubertool-tumblr'
4435
    long_name = 'Ubertool (from Tumblr)'
4436
    url = 'https://ubertool.tumblr.com'
4437
    _categories = ('UBERTOOL', )
4438
4439
4440
class LittleLifeLinesTumblr(GenericDeletedComic, GenericTumblrV1):
4441
    """Class to retrieve Little Life Lines comics."""
4442
    # Also on http://www.littlelifelines.com
4443
    name = 'life-tumblr'
4444
    long_name = 'Little Life Lines (from Tumblr)'
4445
    url = 'https://little-life-lines.tumblr.com'
4446
4447
4448
class TheyCanTalk(GenericTumblrV1):
4449
    """Class to retrieve They Can Talk comics."""
4450
    name = 'theycantalk'
4451
    long_name = 'They Can Talk'
4452
    url = 'http://theycantalk.com'
4453
4454
4455
class Will5NeverCome(GenericTumblrV1):
4456
    """Class to retrieve Will 5:00 Never Come comics."""
4457
    name = 'will5'
4458
    long_name = 'Will 5:00 Never Come ?'
4459
    url = 'http://will5nevercome.com'
4460
4461
4462
class Sephko(GenericTumblrV1):
4463
    """Class to retrieve Sephko Comics."""
4464
    # Also on http://www.sephko.com
4465
    name = 'sephko'
4466
    long_name = 'Sephko'
4467
    url = 'https://sephko.tumblr.com'
4468
4469
4470
class BlazersAtDawn(GenericTumblrV1):
4471
    """Class to retrieve Blazers At Dawn Comics."""
4472
    name = 'blazers'
4473
    long_name = 'Blazers At Dawn'
4474
    url = 'http://blazersatdawn.tumblr.com'
4475
4476
4477
class ArtByMoga(GenericEmptyComic, GenericTumblrV1):  # Deactivated because it downloads too many things
4478
    """Class to retrieve Art By Moga Comics."""
4479
    name = 'moga'
4480
    long_name = 'Art By Moga'
4481
    url = 'http://artbymoga.tumblr.com'
4482
4483
4484
class VerbalVomitTumblr(GenericTumblrV1):
4485
    """Class to retrieve Verbal Vomit comics."""
4486
    # Also on http://www.verbal-vomit.com
4487
    name = 'vomit-tumblr'
4488
    long_name = 'Verbal Vomit (from Tumblr)'
4489
    url = 'http://verbalvomits.tumblr.com'
4490
4491
4492
class LibraryComic(GenericTumblrV1):
4493
    """Class to retrieve LibraryComic."""
4494
    # Also on http://librarycomic.com
4495
    name = 'library-tumblr'
4496
    long_name = 'LibraryComic (from Tumblr)'
4497
    url = 'https://librarycomic.tumblr.com'
4498
4499
4500
class TizzyStitchBirdTumblr(GenericTumblrV1):
4501
    """Class to retrieve Tizzy Stitch Bird comics."""
4502
    # Also on http://tizzystitchbird.com
4503
    # Also on https://tapastic.com/series/TizzyStitchbird
4504
    # Also on http://www.webtoons.com/en/challenge/tizzy-stitchbird/list?title_no=50082
4505
    name = 'tizzy-tumblr'
4506
    long_name = 'Tizzy Stitch Bird (from Tumblr)'
4507
    url = 'http://tizzystitchbird.tumblr.com'
4508
4509
4510
class VictimsOfCircumsolarTumblr(GenericTumblrV1):
4511
    """Class to retrieve VictimsOfCircumsolar comics."""
4512
    # Also on http://www.victimsofcircumsolar.com
4513
    name = 'circumsolar-tumblr'
4514
    long_name = 'Victims Of Circumsolar (from Tumblr)'
4515
    url = 'https://victimsofcomics.tumblr.com'
4516
4517
4518
class RockPaperCynicTumblr(GenericTumblrV1):
4519
    """Class to retrieve RockPaperCynic comics."""
4520
    # Also on http://www.rockpapercynic.com
4521
    # Also on https://tapastic.com/series/rockpapercynic
4522
    name = 'rpc-tumblr'
4523
    long_name = 'Rock Paper Cynic (from Tumblr)'
4524
    url = 'http://rockpapercynic.tumblr.com'
4525
4526
4527
class DeadlyPanelTumblr(GenericTumblrV1):
4528
    """Class to retrieve Deadly Panel comics."""
4529
    # Also on http://www.deadlypanel.com
4530
    # Also on https://tapastic.com/series/deadlypanel
4531
    name = 'deadly-tumblr'
4532
    long_name = 'Deadly Panel (from Tumblr)'
4533
    url = 'https://deadlypanel.tumblr.com'
4534
4535
4536
class CatanaComics(GenericComicNotWorking):  # Not a Tumblr anymore ?
4537
    """Class to retrieve Catana comics."""
4538
    name = 'catana'
4539
    long_name = 'Catana'
4540
    url = 'http://www.catanacomics.com'
4541
4542
4543
class AngryAtNothingTumblr(GenericTumblrV1):
4544
    """Class to retrieve Angry at Nothing comics."""
4545
    # Also on http://www.angryatnothing.net
4546
    # Also on http://tapastic.com/series/Comics-yeah-definitely-comics-
4547
    name = 'angry-tumblr'
4548
    long_name = 'Angry At Nothing (from Tumblr)'
4549
    url = 'http://angryatnothing.tumblr.com'
4550
4551
4552
class ShanghaiTango(GenericTumblrV1):
4553
    """Class to retrieve Shanghai Tango comic."""
4554
    name = 'tango'
4555
    long_name = 'Shanghai Tango'
4556
    url = 'http://tango2010weibo.tumblr.com'
4557
4558
4559
class OffTheLeashDogTumblr(GenericTumblrV1):
4560
    """Class to retrieve Off The Leash Dog comics."""
4561
    # Also on http://offtheleashdogcartoons.com
4562
    # Also on http://www.rupertfawcettcartoons.com
4563
    name = 'offtheleash-tumblr'
4564
    long_name = 'Off The Leash Dog (from Tumblr)'
4565
    url = 'http://rupertfawcettsdoggyblog.tumblr.com'
4566
    _categories = ('FAWCETT', )
4567
4568
4569
class ImogenQuestTumblr(GenericTumblrV1):
4570
    """Class to retrieve Imogen Quest comics."""
4571
    # Also on http://imogenquest.net
4572
    name = 'imogen-tumblr'
4573
    long_name = 'Imogen Quest (from Tumblr)'
4574
    url = 'http://imoquest.tumblr.com'
4575
4576
4577
class Shitfest(GenericTumblrV1):
4578
    """Class to retrieve Shitfest comics."""
4579
    name = 'shitfest'
4580
    long_name = 'Shitfest'
4581
    url = 'http://shitfestcomic.com'
4582
4583
4584
class IceCreamSandwichComics(GenericTumblrV1):
4585
    """Class to retrieve Ice Cream Sandwich Comics."""
4586
    name = 'icecream'
4587
    long_name = 'Ice Cream Sandwich Comics'
4588
    url = 'http://icecreamsandwichcomics.com'
4589
4590
4591
class Dustinteractive(GenericTumblrV1):
4592
    """Class to retrieve Dustinteractive comics."""
4593
    name = 'dustinteractive'
4594
    long_name = 'Dustinteractive'
4595
    url = 'http://dustinteractive.com'
4596
4597
4598
class StickyCinemaFloor(GenericTumblrV1):
4599
    """Class to retrieve Sticky Cinema Floor comics."""
4600
    name = 'stickycinema'
4601
    long_name = 'Sticky Cinema Floor'
4602
    url = 'https://stickycinemafloor.tumblr.com'
4603
4604
4605
class IncidentalComicsTumblr(GenericTumblrV1):
4606
    """Class to retrieve Incidental Comics."""
4607
    # Also on http://www.incidentalcomics.com
4608
    name = 'incidental-tumblr'
4609
    long_name = 'Incidental Comics (from Tumblr)'
4610
    url = 'http://incidentalcomics.tumblr.com'
4611
4612
4613
class APleasantWasteOfTimeTumblr(GenericTumblrV1):
4614
    """Class to retrieve A Pleasant Waste Of Time comics."""
4615
    # Also on https://tapas.io/series/A-Pleasant-
4616
    name = 'pleasant-waste-tumblr'
4617
    long_name = 'A Pleasant Waste Of Time (from Tumblr)'
4618
    url = 'https://artjcf.tumblr.com'
4619
    _categories = ('WASTE', )
4620
4621
4622
class HorovitzComicsTumblr(GenericTumblrV1):
4623
    """Class to retrieve Horovitz new comics."""
4624
    # Also on http://www.horovitzcomics.com
4625
    name = 'horovitz-tumblr'
4626
    long_name = 'Horovitz (from Tumblr)'
4627
    url = 'https://horovitzcomics.tumblr.com'
4628
    _categories = ('HOROVITZ', )
4629
4630
4631
class DeepDarkFearsTumblr(GenericTumblrV1):
4632
    """Class to retrieve DeepvDarkvFears comics."""
4633
    name = 'deep-dark-fears-tumblr'
4634
    long_name = 'Deep Dark Fears (from Tumblr)'
4635
    url = 'http://deep-dark-fears.tumblr.com'
4636
4637
4638
class DakotaMcDadzean(GenericTumblrV1):
4639
    """Class to retrieve Dakota McDadzean comics."""
4640
    name = 'dakota'
4641
    long_name = 'Dakota McDadzean'
4642
    url = 'http://dakotamcfadzean.tumblr.com'
4643
4644
4645
class ExtraFabulousComicsTumblr(GenericTumblrV1):
4646
    """Class to retrieve Extra Fabulous Comics."""
4647
    # Also on http://extrafabulouscomics.com
4648
    name = 'efc-tumblr'
4649
    long_name = 'Extra Fabulous Comics (from Tumblr)'
4650
    url = 'https://extrafabulouscomics.tumblr.com'
4651
    _categories = ('EFC', )
4652
4653
4654
class AlexLevesque(GenericTumblrV1):
4655
    """Class to retrieve AlexLevesque comics."""
4656
    name = 'alevesque'
4657
    long_name = 'Alex Levesque'
4658
    url = 'http://alexlevesque.com'
4659
    _categories = ('FRANCAIS', )
4660
4661
4662
class JamesOfNoTradesTumblr(GenericTumblrV1):
4663
    """Class to retrieve JamesOfNoTrades comics."""
4664
    # Also on http://jamesofnotrades.com
4665
    # Also on http://www.webtoons.com/en/challenge/james-of-no-trades/list?title_no=43422
4666
    # Also on https://tapas.io/series/James-of-No-Trades
4667
    name = 'jamesofnotrades-tumblr'
4668
    long_name = 'James Of No Trades (from Tumblr)'
4669
    url = 'http://jamesfregan.tumblr.com'
4670
    _categories = ('JAMESOFNOTRADES', )
4671
4672
4673
class InfiniteGuff(GenericTumblrV1):
4674
    """Class to retrieve Infinite Guff comics."""
4675
    name = 'infiniteguff'
4676
    long_name = 'Infinite Guff'
4677
    url = 'http://infiniteguff.com'
4678
4679
4680
class SkeletonClaw(GenericTumblrV1):
4681
    """Class to retrieve Skeleton Claw comics."""
4682
    name = 'skeletonclaw'
4683
    long_name = 'Skeleton Claw'
4684
    url = 'http://skeletonclaw.com'
4685
4686
4687
class MrsFrolleinTumblr(GenericTumblrV1):
4688
    """Class to retrieve Mrs Frollein comics."""
4689
    # Also on http://www.webtoons.com/en/challenge/mrsfrollein/list?title_no=51710
4690
    name = 'frollein'
4691
    long_name = 'Mrs Frollein (from Tumblr)'
4692
    url = 'https://mrsfrollein.tumblr.com'
4693
4694
4695
class GoodBearComicsTumblr(GenericTumblrV1):
4696
    """Class to retrieve GoodBearComics."""
4697
    # Also on https://goodbearcomics.com
4698
    name = 'goodbear-tumblr'
4699
    long_name = 'Good Bear Comics (from Tumblr)'
4700
    url = 'https://goodbearcomics.tumblr.com'
4701
4702
4703
class BrooklynCartoonsTumblr(GenericTumblrV1):
4704
    """Class to retrieve Brooklyn Cartoons."""
4705
    # Also on https://www.brooklyncartoons.com
4706
    # Also on https://www.instagram.com/brooklyncartoons
4707
    name = 'brooklyn-tumblr'
4708
    long_name = 'Brooklyn Cartoons (from Tumblr)'
4709
    url = 'http://brooklyncartoons.tumblr.com'
4710
4711
4712
class GemmaCorrellTumblr(GenericTumblrV1):
4713
    # Also on http://www.gemmacorrell.com/portfolio/comics/
4714
    name = 'gemma-tumblr'
4715
    long_name = 'Gemma Correll (from Tumblr)'
4716
    url = 'http://gemmacorrell.tumblr.com'
4717
4718
4719
class RobotatertotTumblr(GenericTumblrV1):
4720
    """Class to retrieve Robotatertot comics."""
4721
    # Also on https://www.instagram.com/robotatertotcomics
4722
    name = 'robotatertot-tumblr'
4723
    long_name = 'Robotatertot (from Tumblr)'
4724
    url = 'https://robotatertot.tumblr.com'
4725
4726
4727
class HuffyPenguin(GenericTumblrV1):
4728
    """Class to retrieve Huffy Penguin comics."""
4729
    name = 'huffypenguin'
4730
    long_name = 'Huffy Penguin'
4731
    url = 'http://huffy-penguin.tumblr.com'
4732
4733
4734
class CowardlyComicsTumblr(GenericTumblrV1):
4735
    """Class to retrieve Cowardly Comics."""
4736
    # Also on https://tapas.io/series/CowardlyComics
4737
    # Also on http://www.webtoons.com/en/challenge/cowardly-comics/list?title_no=65893
4738
    name = 'cowardly-tumblr'
4739
    long_name = 'Cowardly Comics (from Tumblr)'
4740
    url = 'http://cowardlycomics.tumblr.com'
4741
4742
4743
class Caw4hwTumblr(GenericTumblrV1):
4744
    """Class to retrieve Caw4hw comics."""
4745
    # Also on https://tapas.io/series/CAW4HW
4746
    name = 'caw4hw-tumblr'
4747
    long_name = 'Caw4hw (from Tumblr)'
4748
    url = 'https://caw4hw.tumblr.com'
4749
4750
4751
class WeFlapsTumblr(GenericTumblrV1):
4752
    """Class to retrieve WeFlaps comics."""
4753
    name = 'weflaps-tumblr'
4754
    long_name = 'We Flaps (from Tumblr)'
4755
    url = 'https://weflaps.tumblr.com'
4756
4757
4758
class TheseInsideJokesTumblr(GenericTumblrV1):
4759
    """Class to retrieve These Inside Jokes comics."""
4760
    # Also on http://www.theseinsidejokes.com
4761
    name = 'theseinsidejokes-tumblr'
4762
    long_name = 'These Inside Jokes (from Tumblr)'
4763
    url = 'http://theseinsidejokes.tumblr.com'
4764
4765
4766
class SinewynTumblr(GenericTumblrV1):
4767
    """Class to retrieve Sinewyn comics."""
4768
    # Also on https://sinewyn.wordpress.com
4769
    name = 'sinewyn-tumblr'
4770
    long_name = 'Sinewyn (from Tumblr)'
4771
    url = 'https://sinewyn.tumblr.com'
4772
4773
4774
class BoumeriesTumblr(GenericTumblrV1):
4775
    """Class to retrieve Boumeries comics."""
4776
    # Also on http://bd.boumerie.com
4777
    # Also on http://comics.boumerie.com
4778
    name = 'boumeries-tumblr'
4779
    long_name = 'Boumeries (from Tumblr)'
4780
    url = 'http://boumeries.tumblr.com/'
4781
    _categories = ('BOUMERIES', )
4782
4783
4784
class HorovitzComics(GenericDeletedComic, GenericListableComic):
4785
    """Generic class to handle the logic common to the different comics from Horovitz."""
4786
    # Also on https://horovitzcomics.tumblr.com
4787
    url = 'http://www.horovitzcomics.com'
4788
    _categories = ('HOROVITZ', )
4789
    img_re = re.compile('.*comics/([0-9]*)/([0-9]*)/([0-9]*)/.*$')
4790
    link_re = NotImplemented
4791
    get_url_from_archive_element = join_cls_url_to_href
4792
4793
    @classmethod
4794
    def get_comic_info(cls, soup, link):
4795
        """Get information about a particular comics."""
4796
        href = link['href']
4797
        num = int(cls.link_re.match(href).groups()[0])
4798
        title = link.string
4799
        imgs = soup.find_all('img', id='comic')
4800
        assert len(imgs) == 1, imgs
4801
        year, month, day = [int(s)
4802
                            for s in cls.img_re.match(imgs[0]['src']).groups()]
4803
        return {
4804
            'title': title,
4805
            'day': day,
4806
            'month': month,
4807
            'year': year,
4808
            'img': [i['src'] for i in imgs],
4809
            'num': num,
4810
        }
4811
4812
    @classmethod
4813
    def get_archive_elements(cls):
4814
        archive_url = 'http://www.horovitzcomics.com/comics/archive/'
4815
        return reversed(get_soup_at_url(archive_url).find_all('a', href=cls.link_re))
4816
4817
4818
class HorovitzNew(HorovitzComics):
4819
    """Class to retrieve Horovitz new comics."""
4820
    name = 'horovitznew'
4821
    long_name = 'Horovitz New'
4822
    link_re = re.compile('^/comics/new/([0-9]+)$')
4823
4824
4825
class HorovitzClassic(HorovitzComics):
4826
    """Class to retrieve Horovitz classic comics."""
4827
    name = 'horovitzclassic'
4828
    long_name = 'Horovitz Classic'
4829
    link_re = re.compile('^/comics/classic/([0-9]+)$')
4830
4831
4832
class GenericGoComic(GenericNavigableComic):
4833
    """Generic class to handle the logic common to comics from gocomics.com."""
4834
    _categories = ('GOCOMIC', )
4835
4836
    @classmethod
4837
    def get_first_comic_link(cls):
4838
        """Get link to first comics."""
4839
        return get_soup_at_url(cls.url).find('a', class_='fa gc-comic-nav__button fa fa-backward sm ')
4840
4841
    @classmethod
4842
    def get_navi_link(cls, last_soup, next_):
4843
        """Get link to next or previous comic."""
4844
        PREV = 'fa gc-comic-nav__button fa-caret-left sm js-previous-comic '
4845
        NEXT = 'fa gc-comic-nav__button fa-caret-right sm js-next-comic d-sm-none '
4846
        return last_soup.find('a', class_=NEXT if next_ else PREV)
4847
4848
    @classmethod
4849
    def get_url_from_link(cls, link):
4850
        gocomics = 'http://www.gocomics.com'
4851
        return urljoin_wrapper(gocomics, link['href'])
4852
4853
    @classmethod
4854
    def get_comic_info(cls, soup, link):
4855
        """Get information about a particular comics."""
4856
        date_str = soup.find('meta', property='article:published_time')['content']
4857
        day = string_to_date(date_str, "%Y-%m-%d")
4858
        imgs = soup.find('picture', class_='img-fluid item-comic-image').find_all('img')
4859
        author = soup.find('meta', property='article:author')['content']
4860
        tags = soup.find('meta', property='article:tag')['content']
4861
        return {
4862
            'day': day.day,
4863
            'month': day.month,
4864
            'year': day.year,
4865
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
4866
            'author': author,
4867
            'tags': tags,
4868
        }
4869
4870
4871
class PearlsBeforeSwine(GenericGoComic):
4872
    """Class to retrieve Pearls Before Swine comics."""
4873
    name = 'pearls'
4874
    long_name = 'Pearls Before Swine'
4875
    url = 'http://www.gocomics.com/pearlsbeforeswine'
4876
4877
4878
class Peanuts(GenericGoComic):
4879
    """Class to retrieve Peanuts comics."""
4880
    name = 'peanuts'
4881
    long_name = 'Peanuts'
4882
    url = 'http://www.gocomics.com/peanuts'
4883
4884
4885
class MattWuerker(GenericGoComic):
4886
    """Class to retrieve Matt Wuerker comics."""
4887
    name = 'wuerker'
4888
    long_name = 'Matt Wuerker'
4889
    url = 'http://www.gocomics.com/mattwuerker'
4890
4891
4892
class TomToles(GenericGoComic):
4893
    """Class to retrieve Tom Toles comics."""
4894
    name = 'toles'
4895
    long_name = 'Tom Toles'
4896
    url = 'http://www.gocomics.com/tomtoles'
4897
4898
4899
class BreakOfDay(GenericGoComic):
4900
    """Class to retrieve Break Of Day comics."""
4901
    name = 'breakofday'
4902
    long_name = 'Break Of Day'
4903
    url = 'http://www.gocomics.com/break-of-day'
4904
4905
4906
class Brevity(GenericGoComic):
4907
    """Class to retrieve Brevity comics."""
4908
    name = 'brevity'
4909
    long_name = 'Brevity'
4910
    url = 'http://www.gocomics.com/brevity'
4911
4912
4913
class MichaelRamirez(GenericGoComic):
4914
    """Class to retrieve Michael Ramirez comics."""
4915
    name = 'ramirez'
4916
    long_name = 'Michael Ramirez'
4917
    url = 'http://www.gocomics.com/michaelramirez'
4918
4919
4920
class MikeLuckovich(GenericGoComic):
4921
    """Class to retrieve Mike Luckovich comics."""
4922
    name = 'luckovich'
4923
    long_name = 'Mike Luckovich'
4924
    url = 'http://www.gocomics.com/mikeluckovich'
4925
4926
4927
class JimBenton(GenericGoComic):
4928
    """Class to retrieve Jim Benton comics."""
4929
    # Also on http://jimbenton.tumblr.com
4930
    name = 'benton'
4931
    long_name = 'Jim Benton'
4932
    url = 'http://www.gocomics.com/jim-benton-cartoons'
4933
4934
4935
class TheArgyleSweater(GenericGoComic):
4936
    """Class to retrieve the Argyle Sweater comics."""
4937
    name = 'argyle'
4938
    long_name = 'Argyle Sweater'
4939
    url = 'http://www.gocomics.com/theargylesweater'
4940
4941
4942
class SunnyStreet(GenericGoComic):
4943
    """Class to retrieve Sunny Street comics."""
4944
    # Also on http://www.sunnystreetcomics.com
4945
    name = 'sunny'
4946
    long_name = 'Sunny Street'
4947
    url = 'http://www.gocomics.com/sunny-street'
4948
4949
4950
class OffTheMark(GenericGoComic):
4951
    """Class to retrieve Off The Mark comics."""
4952
    # Also on https://www.offthemark.com
4953
    name = 'offthemark'
4954
    long_name = 'Off The Mark'
4955
    url = 'http://www.gocomics.com/offthemark'
4956
4957
4958
class WuMo(GenericGoComic):
4959
    """Class to retrieve WuMo comics."""
4960
    # Also on http://wumo.com
4961
    name = 'wumo'
4962
    long_name = 'WuMo'
4963
    url = 'http://www.gocomics.com/wumo'
4964
4965
4966
class LunarBaboon(GenericGoComic):
4967
    """Class to retrieve Lunar Baboon comics."""
4968
    # Also on http://www.lunarbaboon.com
4969
    # Also on https://tapastic.com/series/Lunarbaboon
4970
    name = 'lunarbaboon'
4971
    long_name = 'Lunar Baboon'
4972
    url = 'http://www.gocomics.com/lunarbaboon'
4973
4974
4975
class SandersenGocomic(GenericGoComic):
4976
    """Class to retrieve Sarah Andersen comics."""
4977
    # Also on http://sarahcandersen.com
4978
    # Also on http://tapastic.com/series/Doodle-Time
4979
    name = 'sandersen-goc'
4980
    long_name = 'Sarah Andersen (from GoComics)'
4981
    url = 'http://www.gocomics.com/sarahs-scribbles'
4982
4983
4984
class SaturdayMorningBreakfastCerealGoComic(GenericGoComic):
4985
    """Class to retrieve Saturday Morning Breakfast Cereal comics."""
4986
    # Also on http://smbc-comics.tumblr.com
4987
    # Also on http://www.smbc-comics.com
4988
    name = 'smbc-goc'
4989
    long_name = 'Saturday Morning Breakfast Cereal (from GoComics)'
4990
    url = 'http://www.gocomics.com/saturday-morning-breakfast-cereal'
4991
    _categories = ('SMBC', )
4992
4993
4994
class CalvinAndHobbesGoComic(GenericGoComic):
4995
    """Class to retrieve Calvin and Hobbes comics."""
4996
    # From gocomics, not http://marcel-oehler.marcellosendos.ch/comics/ch/
4997
    name = 'calvin-goc'
4998
    long_name = 'Calvin and Hobbes (from GoComics)'
4999
    url = 'http://www.gocomics.com/calvinandhobbes'
5000
5001
5002
class RallGoComic(GenericGoComic):
5003
    """Class to retrieve Ted Rall comics."""
5004
    # Also on http://rall.com/comic
5005
    name = 'rall-goc'
5006
    long_name = "Ted Rall (from GoComics)"
5007
    url = "http://www.gocomics.com/ted-rall"
5008
    _categories = ('RALL', )
5009
5010
5011
class TheAwkwardYetiGoComic(GenericGoComic):
5012
    """Class to retrieve The Awkward Yeti comics."""
5013
    # Also on http://larstheyeti.tumblr.com
5014
    # Also on http://theawkwardyeti.com
5015
    # Also on https://tapastic.com/series/TheAwkwardYeti
5016
    name = 'yeti-goc'
5017
    long_name = 'The Awkward Yeti (from GoComics)'
5018
    url = 'http://www.gocomics.com/the-awkward-yeti'
5019
    _categories = ('YETI', )
5020
5021
5022
class BerkeleyMewsGoComics(GenericGoComic):
5023
    """Class to retrieve Berkeley Mews comics."""
5024
    # Also on http://mews.tumblr.com
5025
    # Also on http://www.berkeleymews.com
5026
    name = 'berkeley-goc'
5027
    long_name = 'Berkeley Mews (from GoComics)'
5028
    url = 'http://www.gocomics.com/berkeley-mews'
5029
    _categories = ('BERKELEY', )
5030
5031
5032
class SheldonGoComics(GenericGoComic):
5033
    """Class to retrieve Sheldon comics."""
5034
    # Also on http://www.sheldoncomics.com
5035
    name = 'sheldon-goc'
5036
    long_name = 'Sheldon Comics (from GoComics)'
5037
    url = 'http://www.gocomics.com/sheldon'
5038
5039
5040
class FowlLanguageGoComics(GenericGoComic):
5041
    """Class to retrieve Fowl Language comics."""
5042
    # Also on http://www.fowllanguagecomics.com
5043
    # Also on http://tapastic.com/series/Fowl-Language-Comics
5044
    # Also on http://fowllanguagecomics.tumblr.com
5045
    name = 'fowllanguage-goc'
5046
    long_name = 'Fowl Language Comics (from GoComics)'
5047
    url = 'http://www.gocomics.com/fowl-language'
5048
    _categories = ('FOWLLANGUAGE', )
5049
5050
5051
class NickAnderson(GenericGoComic):
5052
    """Class to retrieve Nick Anderson comics."""
5053
    name = 'nickanderson'
5054
    long_name = 'Nick Anderson'
5055
    url = 'http://www.gocomics.com/nickanderson'
5056
5057
5058
class GarfieldGoComics(GenericGoComic):
5059
    """Class to retrieve Garfield comics."""
5060
    # Also on http://garfield.com
5061
    name = 'garfield-goc'
5062
    long_name = 'Garfield (from GoComics)'
5063
    url = 'http://www.gocomics.com/garfield'
5064
    _categories = ('GARFIELD', )
5065
5066
5067
class DorrisMcGoComics(GenericGoComic):
5068
    """Class to retrieve Dorris Mc Comics"""
5069
    # Also on http://dorrismccomics.com
5070
    name = 'dorrismc-goc'
5071
    long_name = 'Dorris Mc (from GoComics)'
5072
    url = 'http://www.gocomics.com/dorris-mccomics'
5073
5074
5075
class FoxTrot(GenericGoComic):
5076
    """Class to retrieve FoxTrot comics."""
5077
    name = 'foxtrot'
5078
    long_name = 'FoxTrot'
5079
    url = 'http://www.gocomics.com/foxtrot'
5080
5081
5082
class FoxTrotClassics(GenericGoComic):
5083
    """Class to retrieve FoxTrot Classics comics."""
5084
    name = 'foxtrot-classics'
5085
    long_name = 'FoxTrot Classics'
5086
    url = 'http://www.gocomics.com/foxtrotclassics'
5087
5088
5089
class MisterAndMeGoComics(GenericDeletedComic, GenericGoComic):
5090
    """Class to retrieve Mister & Me Comics."""
5091
    # Also on http://www.mister-and-me.com
5092
    # Also on https://tapastic.com/series/Mister-and-Me
5093
    name = 'mister-goc'
5094
    long_name = 'Mister & Me (from GoComics)'
5095
    url = 'http://www.gocomics.com/mister-and-me'
5096
5097
5098
class NonSequitur(GenericGoComic):
5099
    """Class to retrieve Non Sequitur (Wiley Miller) comics."""
5100
    name = 'nonsequitur'
5101
    long_name = 'Non Sequitur'
5102
    url = 'http://www.gocomics.com/nonsequitur'
5103
5104
5105
class JoeyAlisonSayers(GenericGoComic):
5106
    """Class to retrieve Joey Alison Sayers comics."""
5107
    name = 'joeyalison'
5108
    long_name = 'Joey Alison Sayers (from GoComics)'
5109
    url = 'http://www.gocomics.com/joey-alison-sayers-comics'
5110
5111
5112
class SavageChickenGoComics(GenericGoComic):
5113
    """Class to retrieve Savage Chicken comics."""
5114
    # Also on http://www.savagechickens.com
5115
    name = 'savage-goc'
5116
    long_name = 'Savage Chicken (from GoComics)'
5117
    url = 'http://www.gocomics.com/savage-chickens'
5118
5119
5120
class GenericTapasticComic(GenericListableComic):
5121
    """Generic class to handle the logic common to comics from tapastic.com."""
5122
    _categories = ('TAPASTIC', )
5123
5124
    @classmethod
5125
    def get_comic_info(cls, soup, archive_elt):
5126
        """Get information about a particular comics."""
5127
        timestamp = int(archive_elt['publishDate']) / 1000.0
5128
        day = datetime.datetime.fromtimestamp(timestamp).date()
5129
        imgs = soup.find_all('img', class_='art-image')
5130
        if not imgs:
5131
            # print("Comic %s is being uploaded, retry later" % cls.get_url_from_archive_element(archive_elt))
5132
            return None
5133
        assert len(imgs) > 0, imgs
5134
        return {
5135
            'day': day.day,
5136
            'year': day.year,
5137
            'month': day.month,
5138
            'img': [i['src'] for i in imgs],
5139
            'title': archive_elt['title'],
5140
        }
5141
5142
    @classmethod
5143
    def get_url_from_archive_element(cls, archive_elt):
5144
        return 'http://tapastic.com/episode/' + str(archive_elt['id'])
5145
5146
    @classmethod
5147
    def get_archive_elements(cls):
5148
        pref, suff = 'episodeList : ', ','
5149
        # Information is stored in the javascript part
5150
        # I don't know the clean way to get it so this is the ugly way.
5151
        string = [s[len(pref):-len(suff)] for s in (s.decode('utf-8').strip() for s in urlopen_wrapper(cls.url).readlines()) if s.startswith(pref) and s.endswith(suff)][0]
5152
        return json.loads(string)
5153
5154
5155
class VegetablesForDessert(GenericTapasticComic):
5156
    """Class to retrieve Vegetables For Dessert comics."""
5157
    # Also on http://vegetablesfordessert.tumblr.com
5158
    name = 'vegetables'
5159
    long_name = 'Vegetables For Dessert'
5160
    url = 'http://tapastic.com/series/vegetablesfordessert'
5161
5162
5163
class FowlLanguageTapa(GenericTapasticComic):
5164
    """Class to retrieve Fowl Language comics."""
5165
    # Also on http://www.fowllanguagecomics.com
5166
    # Also on http://fowllanguagecomics.tumblr.com
5167
    # Also on http://www.gocomics.com/fowl-language
5168
    name = 'fowllanguage-tapa'
5169
    long_name = 'Fowl Language Comics (from Tapastic)'
5170
    url = 'http://tapastic.com/series/Fowl-Language-Comics'
5171
    _categories = ('FOWLLANGUAGE', )
5172
5173
5174
class OscillatingProfundities(GenericTapasticComic):
5175
    """Class to retrieve Oscillating Profundities comics."""
5176
    name = 'oscillating'
5177
    long_name = 'Oscillating Profundities'
5178
    url = 'http://tapastic.com/series/oscillatingprofundities'
5179
5180
5181
class ZnoflatsComics(GenericTapasticComic):
5182
    """Class to retrieve Znoflats comics."""
5183
    name = 'znoflats'
5184
    long_name = 'Znoflats Comics'
5185
    url = 'http://tapastic.com/series/Znoflats-Comics'
5186
5187
5188
class SandersenTapastic(GenericTapasticComic):
5189
    """Class to retrieve Sarah Andersen comics."""
5190
    # Also on http://sarahcandersen.com
5191
    # Also on http://www.gocomics.com/sarahs-scribbles
5192
    name = 'sandersen-tapa'
5193
    long_name = 'Sarah Andersen (from Tapastic)'
5194
    url = 'http://tapastic.com/series/Doodle-Time'
5195
5196
5197
class TubeyToonsTapastic(GenericTapasticComic):
5198
    """Class to retrieve TubeyToons comics."""
5199
    # Also on http://tubeytoons.com
5200
    # Also on https://tubeytoons.tumblr.com
5201
    name = 'tubeytoons-tapa'
5202
    long_name = 'Tubey Toons (from Tapastic)'
5203
    url = 'http://tapastic.com/series/Tubey-Toons'
5204
    _categories = ('TUNEYTOONS', )
5205
5206
5207
class AnythingComicTapastic(GenericTapasticComic):
5208
    """Class to retrieve Anything Comics."""
5209
    # Also on http://www.anythingcomic.com
5210
    name = 'anythingcomic-tapa'
5211
    long_name = 'Anything Comic (from Tapastic)'
5212
    url = 'http://tapastic.com/series/anything'
5213
5214
5215
class UnearthedComicsTapastic(GenericTapasticComic):
5216
    """Class to retrieve Unearthed comics."""
5217
    # Also on http://unearthedcomics.com
5218
    # Also on https://unearthedcomics.tumblr.com
5219
    name = 'unearthed-tapa'
5220
    long_name = 'Unearthed Comics (from Tapastic)'
5221
    url = 'http://tapastic.com/series/UnearthedComics'
5222
    _categories = ('UNEARTHED', )
5223
5224
5225
class EverythingsStupidTapastic(GenericTapasticComic):
5226
    """Class to retrieve Everything's stupid Comics."""
5227
    # Also on http://www.webtoons.com/en/challenge/everythings-stupid/list?title_no=14591
5228
    # Also on http://everythingsstupid.net
5229
    name = 'stupid-tapa'
5230
    long_name = "Everything's Stupid (from Tapastic)"
5231
    url = 'http://tapastic.com/series/EverythingsStupid'
5232
5233
5234
class JustSayEhTapastic(GenericTapasticComic):
5235
    """Class to retrieve Just Say Eh comics."""
5236
    # Also on http://www.justsayeh.com
5237
    name = 'justsayeh-tapa'
5238
    long_name = 'Just Say Eh (from Tapastic)'
5239
    url = 'http://tapastic.com/series/Just-Say-Eh'
5240
5241
5242
class ThorsThundershackTapastic(GenericTapasticComic):
5243
    """Class to retrieve Thor's Thundershack comics."""
5244
    # Also on http://www.thorsthundershack.com
5245
    name = 'thor-tapa'
5246
    long_name = 'Thor\'s Thundershack (from Tapastic)'
5247
    url = 'http://tapastic.com/series/Thors-Thundershac'
5248
    _categories = ('THOR', )
5249
5250
5251
class OwlTurdTapastic(GenericTapasticComic):
5252
    """Class to retrieve Owl Turd comics."""
5253
    # Also on http://owlturd.com
5254
    name = 'owlturd-tapa'
5255
    long_name = 'Owl Turd (from Tapastic)'
5256
    url = 'http://tapastic.com/series/Owl-Turd-Comix'
5257
    _categories = ('OWLTURD', )
5258
5259
5260
class GoneIntoRaptureTapastic(GenericTapasticComic):
5261
    """Class to retrieve Gone Into Rapture comics."""
5262
    # Also on http://goneintorapture.tumblr.com
5263
    # Also on http://goneintorapture.com
5264
    name = 'rapture-tapa'
5265
    long_name = 'Gone Into Rapture (from Tapastic)'
5266
    url = 'http://tapastic.com/series/Goneintorapture'
5267
5268
5269
class HeckIfIKnowComicsTapa(GenericTapasticComic):
5270
    """Class to retrieve Heck If I Know Comics."""
5271
    # Also on http://heckifiknowcomics.com
5272
    name = 'heck-tapa'
5273
    long_name = 'Heck if I Know comics (from Tapastic)'
5274
    url = 'http://tapastic.com/series/Regular'
5275
5276
5277
class CheerUpEmoKidTapa(GenericTapasticComic):
5278
    """Class to retrieve CheerUpEmoKid comics."""
5279
    # Also on http://www.cheerupemokid.com
5280
    # Also on https://enzocomics.tumblr.com
5281
    name = 'cuek-tapa'
5282
    long_name = 'Cheer Up Emo Kid (from Tapastic)'
5283
    url = 'http://tapastic.com/series/CUEK'
5284
5285
5286
class BigFootJusticeTapa(GenericTapasticComic):
5287
    """Class to retrieve Big Foot Justice comics."""
5288
    # Also on http://bigfootjustice.com
5289
    name = 'bigfoot-tapa'
5290
    long_name = 'Big Foot Justice (from Tapastic)'
5291
    url = 'http://tapastic.com/series/bigfoot-justice'
5292
5293
5294
class UpAndOutTapa(GenericTapasticComic):
5295
    """Class to retrieve Up & Out comics."""
5296
    # Also on http://upandoutcomic.tumblr.com
5297
    name = 'upandout-tapa'
5298
    long_name = 'Up And Out (from Tapastic)'
5299
    url = 'http://tapastic.com/series/UP-and-OUT'
5300
5301
5302
class ToonHoleTapa(GenericTapasticComic):
5303
    """Class to retrieve Toon Holes comics."""
5304
    # Also on http://www.toonhole.com
5305
    name = 'toonhole-tapa'
5306
    long_name = 'Toon Hole (from Tapastic)'
5307
    url = 'http://tapastic.com/series/TOONHOLE'
5308
5309
5310
class AngryAtNothingTapa(GenericTapasticComic):
5311
    """Class to retrieve Angry at Nothing comics."""
5312
    # Also on http://www.angryatnothing.net
5313
    # Also on http://angryatnothing.tumblr.com
5314
    name = 'angry-tapa'
5315
    long_name = 'Angry At Nothing (from Tapastic)'
5316
    url = 'http://tapastic.com/series/Comics-yeah-definitely-comics-'
5317
5318
5319
class LeleozTapa(GenericTapasticComic):
5320
    """Class to retrieve Leleoz comics."""
5321
    # Also on http://leleozcomics.tumblr.com
5322
    name = 'leleoz-tapa'
5323
    long_name = 'Leleoz (from Tapastic)'
5324
    url = 'https://tapastic.com/series/Leleoz'
5325
5326
5327
class TheAwkwardYetiTapa(GenericTapasticComic):
5328
    """Class to retrieve The Awkward Yeti comics."""
5329
    # Also on http://www.gocomics.com/the-awkward-yeti
5330
    # Also on http://theawkwardyeti.com
5331
    # Also on http://larstheyeti.tumblr.com
5332
    name = 'yeti-tapa'
5333
    long_name = 'The Awkward Yeti (from Tapastic)'
5334
    url = 'https://tapastic.com/series/TheAwkwardYeti'
5335
    _categories = ('YETI', )
5336
5337
5338
class AsPerUsualTapa(GenericTapasticComic):
5339
    """Class to retrieve As Per Usual comics."""
5340
    # Also on http://as-per-usual.tumblr.com
5341
    name = 'usual-tapa'
5342
    long_name = 'As Per Usual (from Tapastic)'
5343
    url = 'https://tapastic.com/series/AsPerUsual'
5344
    categories = ('DAMILEE', )
5345
5346
5347
class HotComicsForCoolPeopleTapa(GenericTapasticComic):
5348
    """Class to retrieve Hot Comics For Cool People."""
5349
    # Also on http://hotcomicsforcoolpeople.tumblr.com
5350
    # Also on http://hotcomics.biz (links to tumblr)
5351
    # Also on http://hcfcp.com (links to tumblr)
5352
    name = 'hotcomics-tapa'
5353
    long_name = 'Hot Comics For Cool People (from Tapastic)'
5354
    url = 'https://tapastic.com/series/Hot-Comics-For-Cool-People'
5355
    categories = ('DAMILEE', )
5356
5357
5358
class OneOneOneOneComicTapa(GenericTapasticComic):
5359
    """Class to retrieve 1111 Comics."""
5360
    # Also on http://www.1111comics.me
5361
    # Also on http://comics1111.tumblr.com
5362
    name = '1111-tapa'
5363
    long_name = '1111 Comics (from Tapastic)'
5364
    url = 'https://tapastic.com/series/1111-Comics'
5365
    _categories = ('ONEONEONEONE', )
5366
5367
5368
class TumbleDryTapa(GenericTapasticComic):
5369
    """Class to retrieve Tumble Dry comics."""
5370
    # Also on http://tumbledrycomics.com
5371
    name = 'tumbledry-tapa'
5372
    long_name = 'Tumblr Dry (from Tapastic)'
5373
    url = 'https://tapastic.com/series/TumbleDryComics'
5374
5375
5376
class DeadlyPanelTapa(GenericTapasticComic):
5377
    """Class to retrieve Deadly Panel comics."""
5378
    # Also on http://www.deadlypanel.com
5379
    # Also on https://deadlypanel.tumblr.com
5380
    name = 'deadly-tapa'
5381
    long_name = 'Deadly Panel (from Tapastic)'
5382
    url = 'https://tapastic.com/series/deadlypanel'
5383
5384
5385
class ChrisHallbeckMaxiTapa(GenericTapasticComic):
5386
    """Class to retrieve Chris Hallbeck comics."""
5387
    # Also on https://chrishallbeck.tumblr.com
5388
    # Also on http://maximumble.com
5389
    name = 'hallbeckmaxi-tapa'
5390
    long_name = 'Chris Hallback - Maximumble (from Tapastic)'
5391
    url = 'https://tapastic.com/series/Maximumble'
5392
    _categories = ('HALLBACK', )
5393
5394
5395
class ChrisHallbeckMiniTapa(GenericDeletedComic, GenericTapasticComic):
5396
    """Class to retrieve Chris Hallbeck comics."""
5397
    # Also on https://chrishallbeck.tumblr.com
5398
    # Also on http://minimumble.com
5399
    name = 'hallbeckmini-tapa'
5400
    long_name = 'Chris Hallback - Minimumble (from Tapastic)'
5401
    url = 'https://tapastic.com/series/Minimumble'
5402
    _categories = ('HALLBACK', )
5403
5404
5405
class ChrisHallbeckBiffTapa(GenericDeletedComic, GenericTapasticComic):
5406
    """Class to retrieve Chris Hallbeck comics."""
5407
    # Also on https://chrishallbeck.tumblr.com
5408
    # Also on http://thebookofbiff.com
5409
    name = 'hallbeckbiff-tapa'
5410
    long_name = 'Chris Hallback - The Book of Biff (from Tapastic)'
5411
    url = 'https://tapastic.com/series/Biff'
5412
    _categories = ('HALLBACK', )
5413
5414
5415
class RandoWisTapa(GenericTapasticComic):
5416
    """Class to retrieve RandoWis comics."""
5417
    # Also on https://randowis.com
5418
    name = 'randowis-tapa'
5419
    long_name = 'RandoWis (from Tapastic)'
5420
    url = 'https://tapastic.com/series/RandoWis'
5421
5422
5423
class PigeonGazetteTapa(GenericTapasticComic):
5424
    """Class to retrieve The Pigeon Gazette comics."""
5425
    # Also on http://thepigeongazette.tumblr.com
5426
    name = 'pigeon-tapa'
5427
    long_name = 'The Pigeon Gazette (from Tapastic)'
5428
    url = 'https://tapastic.com/series/The-Pigeon-Gazette'
5429
5430
5431
class TheOdd1sOutTapa(GenericTapasticComic):
5432
    """Class to retrieve The Odd 1s Out comics."""
5433
    # Also on http://theodd1sout.com
5434
    # Also on http://theodd1sout.tumblr.com
5435
    name = 'theodd-tapa'
5436
    long_name = 'The Odd 1s Out (from Tapastic)'
5437
    url = 'https://tapastic.com/series/Theodd1sout'
5438
5439
5440
class TheWorldIsFlatTapa(GenericTapasticComic):
5441
    """Class to retrieve The World Is Flat Comics."""
5442
    # Also on http://theworldisflatcomics.tumblr.com
5443
    name = 'flatworld-tapa'
5444
    long_name = 'The World Is Flat (from Tapastic)'
5445
    url = 'https://tapastic.com/series/The-World-is-Flat'
5446
5447
5448
class MisterAndMeTapa(GenericTapasticComic):
5449
    """Class to retrieve Mister & Me Comics."""
5450
    # Also on http://www.mister-and-me.com
5451
    # Also on http://www.gocomics.com/mister-and-me
5452
    name = 'mister-tapa'
5453
    long_name = 'Mister & Me (from Tapastic)'
5454
    url = 'https://tapastic.com/series/Mister-and-Me'
5455
5456
5457
class TalesOfAbsurdityTapa(GenericDeletedComic, GenericTapasticComic):
5458
    """Class to retrieve Tales Of Absurdity comics."""
5459
    # Also on http://talesofabsurdity.com
5460
    # Also on http://talesofabsurdity.tumblr.com
5461
    name = 'absurdity-tapa'
5462
    long_name = 'Tales of Absurdity (from Tapastic)'
5463
    url = 'http://tapastic.com/series/Tales-Of-Absurdity'
5464
    _categories = ('ABSURDITY', )
5465
5466
5467
class BFGFSTapa(GenericTapasticComic):
5468
    """Class to retrieve BFGFS comics."""
5469
    # Also on http://bfgfs.com
5470
    # Also on https://bfgfs.tumblr.com
5471
    name = 'bfgfs-tapa'
5472
    long_name = 'BFGFS (from Tapastic)'
5473
    url = 'https://tapastic.com/series/BFGFS'
5474
5475
5476
class DoodleForFoodTapa(GenericTapasticComic):
5477
    """Class to retrieve Doodle For Food comics."""
5478
    # Also on http://www.doodleforfood.com
5479
    name = 'doodle-tapa'
5480
    long_name = 'Doodle For Food (from Tapastic)'
5481
    url = 'https://tapastic.com/series/Doodle-for-Food'
5482
5483
5484
class MrLovensteinTapa(GenericTapasticComic):
5485
    """Class to retrieve Mr Lovenstein comics."""
5486
    # Also on  https://tapastic.com/series/MrLovenstein
5487
    name = 'mrlovenstein-tapa'
5488
    long_name = 'Mr. Lovenstein (from Tapastic)'
5489
    url = 'https://tapastic.com/series/MrLovenstein'
5490
5491
5492
class CassandraCalinTapa(GenericTapasticComic):
5493
    """Class to retrieve C. Cassandra comics."""
5494
    # Also on http://cassandracalin.com
5495
    # Also on http://c-cassandra.tumblr.com
5496
    name = 'cassandra-tapa'
5497
    long_name = 'Cassandra Calin (from Tapastic)'
5498
    url = 'https://tapastic.com/series/C-Cassandra-comics'
5499
5500
5501
class WafflesAndPancakes(GenericTapasticComic):
5502
    """Class to retrieve Waffles And Pancakes comics."""
5503
    # Also on http://wandpcomic.com
5504
    name = 'waffles'
5505
    long_name = 'Waffles And Pancakes'
5506
    url = 'https://tapastic.com/series/Waffles-and-Pancakes'
5507
5508
5509
class YesterdaysPopcornTapastic(GenericTapasticComic):
5510
    """Class to retrieve Yesterday's Popcorn comics."""
5511
    # Also on http://www.yesterdayspopcorn.com
5512
    # Also on http://yesterdayspopcorn.tumblr.com
5513
    name = 'popcorn-tapa'
5514
    long_name = 'Yesterday\'s Popcorn (from Tapastic)'
5515
    url = 'https://tapastic.com/series/Yesterdays-Popcorn'
5516
5517
5518
class OurSuperAdventureTapastic(GenericDeletedComic, GenericTapasticComic):
5519
    """Class to retrieve Our Super Adventure comics."""
5520
    # Also on http://www.oursuperadventure.com
5521
    # http://sarahssketchbook.tumblr.com
5522
    # http://sarahgraley.com
5523
    name = 'superadventure-tapastic'
5524
    long_name = 'Our Super Adventure (from Tapastic)'
5525
    url = 'https://tapastic.com/series/Our-Super-Adventure'
5526
5527
5528
class NamelessPCs(GenericTapasticComic):
5529
    """Class to retrieve Nameless PCs comics."""
5530
    # Also on http://namelesspcs.com
5531
    name = 'namelesspcs-tapa'
5532
    long_name = 'NamelessPCs (from Tapastic)'
5533
    url = 'https://tapastic.com/series/NamelessPC'
5534
5535
5536
class DownTheUpwardSpiralTapa(GenericTapasticComic):
5537
    """Class to retrieve Down The Upward Spiral comics."""
5538
    # Also on http://www.downtheupwardspiral.com
5539
    # Also on http://downtheupwardspiral.tumblr.com
5540
    name = 'spiral-tapa'
5541
    long_name = 'Down the Upward Spiral (from Tapastic)'
5542
    url = 'https://tapastic.com/series/Down-the-Upward-Spiral'
5543
5544
5545
class UbertoolTapa(GenericTapasticComic):
5546
    """Class to retrieve Ubertool comics."""
5547
    # Also on http://ubertoolcomic.com
5548
    # Also on https://ubertool.tumblr.com
5549
    name = 'ubertool-tapa'
5550
    long_name = 'Ubertool (from Tapastic)'
5551
    url = 'https://tapastic.com/series/ubertool'
5552
    _categories = ('UBERTOOL', )
5553
5554
5555
class BarteNerdsTapa(GenericDeletedComic, GenericTapasticComic):
5556
    """Class to retrieve BarteNerds comics."""
5557
    # Also on http://www.bartenerds.com
5558
    name = 'bartenerds-tapa'
5559
    long_name = 'BarteNerds (from Tapastic)'
5560
    url = 'https://tapastic.com/series/BarteNERDS'
5561
5562
5563
class SmallBlueYonderTapa(GenericTapasticComic):
5564
    """Class to retrieve Small Blue Yonder comics."""
5565
    # Also on http://www.smallblueyonder.com
5566
    name = 'smallblue-tapa'
5567
    long_name = 'Small Blue Yonder (from Tapastic)'
5568
    url = 'https://tapastic.com/series/Small-Blue-Yonder'
5569
5570
5571
class TizzyStitchBirdTapa(GenericTapasticComic):
5572
    """Class to retrieve Tizzy Stitch Bird comics."""
5573
    # Also on http://tizzystitchbird.com
5574
    # Also on http://tizzystitchbird.tumblr.com
5575
    # Also on http://www.webtoons.com/en/challenge/tizzy-stitchbird/list?title_no=50082
5576
    name = 'tizzy-tapa'
5577
    long_name = 'Tizzy Stitch Bird (from Tapastic)'
5578
    url = 'https://tapastic.com/series/TizzyStitchbird'
5579
5580
5581
class RockPaperCynicTapa(GenericTapasticComic):
5582
    """Class to retrieve RockPaperCynic comics."""
5583
    # Also on http://www.rockpapercynic.com
5584
    # Also on http://rockpapercynic.tumblr.com
5585
    name = 'rpc-tapa'
5586
    long_name = 'Rock Paper Cynic (from Tapastic)'
5587
    url = 'https://tapastic.com/series/rockpapercynic'
5588
5589
5590
class IsItCanonTapa(GenericTapasticComic):
5591
    """Class to retrieve Is It Canon comics."""
5592
    # Also on http://www.isitcanon.com
5593
    name = 'canon-tapa'
5594
    long_name = 'Is It Canon (from Tapastic)'
5595
    url = 'http://tapastic.com/series/isitcanon'
5596
5597
5598
class ItsTheTieTapa(GenericTapasticComic):
5599
    """Class to retrieve It's the tie comics."""
5600
    # Also on http://itsthetie.com
5601
    # Also on http://itsthetie.tumblr.com
5602
    name = 'tie-tapa'
5603
    long_name = "It's the tie (from Tapastic)"
5604
    url = "https://tapastic.com/series/itsthetie"
5605
    _categories = ('TIE', )
5606
5607
5608
class JamesOfNoTradesTapa(GenericTapasticComic):
5609
    """Class to retrieve JamesOfNoTrades comics."""
5610
    # Also on http://jamesofnotrades.com
5611
    # Also on http://www.webtoons.com/en/challenge/james-of-no-trades/list?title_no=43422
5612
    # Also on http://jamesfregan.tumblr.com
5613
    name = 'jamesofnotrades-tapa'
5614
    long_name = 'James Of No Trades (from Tapastic)'
5615
    url = 'https://tapas.io/series/James-of-No-Trades'
5616
    _categories = ('JAMESOFNOTRADES', )
5617
5618
5619
class MomentumTapa(GenericTapasticComic):
5620
    """Class to retrieve Momentum comics."""
5621
    # Also on http://www.momentumcomic.com
5622
    name = 'momentum-tapa'
5623
    long_name = 'Momentum (from Tapastic)'
5624
    url = 'https://tapastic.com/series/momentum'
5625
5626
5627
class InYourFaceCakeTapa(GenericTapasticComic):
5628
    """Class to retrieve In Your Face Cake comics."""
5629
    # Also on https://in-your-face-cake.tumblr.com
5630
    name = 'inyourfacecake-tapa'
5631
    long_name = 'In Your Face Cake (from Tapastic)'
5632
    url = 'https://tapas.io/series/In-Your-Face-Cake'
5633
    _categories = ('INYOURFACECAKE', )
5634
5635
5636
class CowardlyComicsTapa(GenericTapasticComic):
5637
    """Class to retrieve Cowardly Comics."""
5638
    # Also on http://cowardlycomics.tumblr.com
5639
    # Also on http://www.webtoons.com/en/challenge/cowardly-comics/list?title_no=65893
5640
    name = 'cowardly-tapa'
5641
    long_name = 'Cowardly Comics (from Tapastic)'
5642
    url = 'https://tapas.io/series/CowardlyComics'
5643
5644
5645
class Caw4hwTapa(GenericTapasticComic):
5646
    """Class to retrieve Caw4hw comics."""
5647
    # Also on https://caw4hw.tumblr.com
5648
    name = 'caw4hw-tapa'
5649
    long_name = 'Caw4hw (from Tapastic)'
5650
    url = 'https://tapas.io/series/CAW4HW'
5651
5652
5653
class APleasantWasteOfTimeTapa(GenericTapasticComic):
5654
    """Class to retrieve A Pleasant Waste Of Time comics."""
5655
    # Also on https://artjcf.tumblr.com
5656
    name = 'pleasant-waste-tapa'
5657
    long_name = 'A Pleasant Waste Of Time (from Tapastic)'
5658
    url = 'https://tapas.io/series/A-Pleasant-'
5659
    _categories = ('WASTE', )
5660
5661
5662
class AbsurdoLapin(GenericNavigableComic):
5663
    """Class to retrieve Absurdo Lapin comics."""
5664
    name = 'absurdo'
5665
    long_name = 'Absurdo'
5666
    url = 'https://absurdo.lapin.org'
5667
    get_url_from_link = join_cls_url_to_href
5668
5669
    @classmethod
5670
    def get_nav(cls, soup):
5671
        """Get the navigation elements from soup object."""
5672
        cont = soup.find('div', id='content')
5673
        _, b2 = cont.find_all('div', class_='buttons')
5674
        # prev, first, last, next
5675
        return [li.find('a') for li in b2.find_all('li')]
5676
5677
    @classmethod
5678
    def get_first_comic_link(cls):
5679
        """Get link to first comics."""
5680
        return cls.get_nav(get_soup_at_url(cls.url))[1]
5681
5682
    @classmethod
5683
    def get_navi_link(cls, last_soup, next_):
5684
        """Get link to next or previous comic."""
5685
        return cls.get_nav(last_soup)[3 if next_ else 0]
5686
5687
    @classmethod
5688
    def get_comic_info(cls, soup, link):
5689
        """Get information about a particular comics."""
5690
        author = soup.find('meta', attrs={'name': 'author'})['content']
5691
        tags = soup.find('meta', attrs={'name': 'keywords'})['content']
5692
        title = soup.find('title').string
5693
        imgs = soup.find('div', id='content').find_all('img')
5694
        return {
5695
            'title': title,
5696
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
5697
            'tags': tags,
5698
            'author': author,
5699
        }
5700
5701
5702
def get_subclasses(klass):
5703
    """Gets the list of direct/indirect subclasses of a class"""
5704
    subclasses = klass.__subclasses__()
5705
    for derived in list(subclasses):
5706
        subclasses.extend(get_subclasses(derived))
5707
    return subclasses
5708
5709
5710
def remove_st_nd_rd_th_from_date(string):
5711
    """Function to transform 1st/2nd/3rd/4th in a parsable date format."""
5712
    # Hackish way to convert string with numeral "1st"/"2nd"/etc to date
5713
    return (string.replace('st', '')
5714
            .replace('nd', '')
5715
            .replace('rd', '')
5716
            .replace('th', '')
5717
            .replace('Augu', 'August'))
5718
5719
5720
def string_to_date(string, date_format, local=DEFAULT_LOCAL):
5721
    """Function to convert string to date object.
5722
    Wrapper around datetime.datetime.strptime."""
5723
    # format described in https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior
5724
    prev_locale = locale.setlocale(locale.LC_ALL)
5725
    if local != prev_locale:
5726
        locale.setlocale(locale.LC_ALL, local)
5727
    ret = datetime.datetime.strptime(string, date_format).date()
5728
    if local != prev_locale:
5729
        locale.setlocale(locale.LC_ALL, prev_locale)
5730
    return ret
5731
5732
5733
COMICS = set(get_subclasses(GenericComic))
5734
VALID_COMICS = [c for c in COMICS if c.name is not None]
5735
COMIC_NAMES = {c.name: c for c in VALID_COMICS}
5736
assert len(VALID_COMICS) == len(COMIC_NAMES)
5737
CLASS_NAMES = {c.__name__ for c in VALID_COMICS}
5738
assert len(VALID_COMICS) == len(CLASS_NAMES)
5739