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

comics.py (10 issues)

Code
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
class ExtraFabulousComics(GenericNavigableComic):
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
class GenericLeMondeBlog(GenericNavigableComic):
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
class Rall(GenericComicNotWorking, GenericNavigableComic):
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
class OneOneOneOneComic(GenericComicNotWorking, GenericNavigableComic):
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
class AngryAtNothing(GenericDeletedComic, GenericNavigableComic):
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
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
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
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
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
class TheGentlemanArmchair(GenericNavigableComic):
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
class ImogenQuest(GenericNavigableComic):
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
class PerryBibleFellowship(GenericListableComic):  # Is now navigable too
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
class Mercworks(GenericNavigableComic):
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
class AmazingSuperPowers(GenericNavigableComic):
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)
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
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:
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
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 View Code Duplication
class MonkeyUser(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
1654
    """Class to retrieve Monkey User comics."""
1655
    name = 'monkeyuser'
1656
    long_name = 'Monkey User'
1657
    url = 'http://www.monkeyuser.com'
1658
    get_first_comic_link = simulate_first_link
1659
    first_url = 'http://www.monkeyuser.com/2016/project-lifecycle/'
1660
    get_url_from_link = join_cls_url_to_href
1661
1662
    @classmethod
1663
    def get_navi_link(cls, last_soup, next_):
1664
        """Get link to next or previous comic."""
1665
        div = last_soup.find('div', title='next' if next_ else 'previous')
1666
        return None if div is None else div.find('a')
1667
1668
    @classmethod
1669
    def get_comic_info(cls, soup, link):
1670
        """Get information about a particular comics."""
1671
        title = soup.find('meta', property='og:title')['content']
1672
        desc = soup.find('meta', property='og:description')['content']
1673
        imgs = soup.find_all('meta', property='og:image')
1674
        date_str = soup.find('span', class_='post-date').find('time').string
1675
        day = string_to_date(date_str, "%d %b %Y")
1676
        return {
1677
            'month': day.month,
1678
            'year': day.year,
1679
            'day': day.day,
1680
            'img': [i['content'] for i in imgs],
1681
            'title': title,
1682
            'description': desc,
1683
        }
1684
1685
1686
class InfiniteMonkeyBusiness(GenericNavigableComic):
1687
    """Class to retrieve InfiniteMonkeyBusiness comics."""
1688
    name = 'monkey'
1689
    long_name = 'Infinite Monkey Business'
1690
    url = 'http://infinitemonkeybusiness.net'
1691
    get_navi_link = get_a_navi_comicnavnext_navinext
1692
    get_first_comic_link = simulate_first_link
1693
    first_url = 'http://infinitemonkeybusiness.net/comic/pillory/'
1694
1695
    @classmethod
1696
    def get_comic_info(cls, soup, link):
1697
        """Get information about a particular comics."""
1698
        title = soup.find('meta', property='og:title')['content']
1699
        imgs = soup.find('div', id='comic').find_all('img')
1700
        return {
1701
            'title': title,
1702
            'img': [i['src'] for i in imgs],
1703
        }
1704
1705
1706
class Wondermark(GenericListableComic):
1707
    """Class to retrieve the Wondermark comics."""
1708
    name = 'wondermark'
1709
    long_name = 'Wondermark'
1710
    url = 'http://wondermark.com'
1711
    get_url_from_archive_element = get_href
1712
1713
    @classmethod
1714
    def get_archive_elements(cls):
1715
        archive_url = urljoin_wrapper(cls.url, 'archive/')
1716
        return reversed(get_soup_at_url(archive_url).find_all('a', rel='bookmark'))
1717
1718
    @classmethod
1719
    def get_comic_info(cls, soup, link):
1720
        """Get information about a particular comics."""
1721
        date_str = soup.find('div', class_='postdate').find('em').string
1722
        day = string_to_date(remove_st_nd_rd_th_from_date(date_str), "%B %d, %Y")
1723
        div = soup.find('div', id='comic')
1724
        if div:
1725
            img = div.find('img')
1726
            img_src = [img['src']]
1727
            alt = img['alt']
1728
            assert alt == img['title']
1729
            title = soup.find('meta', property='og:title')['content']
1730
        else:
1731
            img_src = []
1732
            alt = ''
1733
            title = ''
1734
        return {
1735
            'month': day.month,
1736
            'year': day.year,
1737
            'day': day.day,
1738
            'img': img_src,
1739
            'title': title,
1740
            'alt': alt,
1741
            'tags': ' '.join(t.string for t in soup.find('div', class_='postmeta').find_all('a', rel='tag')),
1742
        }
1743
1744
1745
class WarehouseComic(GenericNavigableComic):
1746
    """Class to retrieve Warehouse Comic comics."""
1747
    name = 'warehouse'
1748
    long_name = 'Warehouse Comic'
1749
    url = 'http://warehousecomic.com'
1750
    get_first_comic_link = get_a_navi_navifirst
1751
    get_navi_link = get_link_rel_next
1752
1753
    @classmethod
1754
    def get_comic_info(cls, soup, link):
1755
        """Get information about a particular comics."""
1756
        title = soup.find('h2', class_='post-title').string
1757
        date_str = soup.find('span', class_='post-date').string
1758
        day = string_to_date(date_str, "%B %d, %Y")
1759
        imgs = soup.find('div', id='comic').find_all('img')
1760
        return {
1761
            'img': [i['src'] for i in imgs],
1762
            'title': title,
1763
            'day': day.day,
1764
            'month': day.month,
1765
            'year': day.year,
1766
        }
1767
1768
1769
class JustSayEh(GenericNavigableComic):
1770
    """Class to retrieve Just Say Eh comics."""
1771
    # Also on http//tapastic.com/series/Just-Say-Eh
1772
    name = 'justsayeh'
1773
    long_name = 'Just Say Eh'
1774
    url = 'http://www.justsayeh.com'
1775
    get_first_comic_link = get_a_navi_navifirst
1776
    get_navi_link = get_a_navi_comicnavnext_navinext
1777
1778
    @classmethod
1779
    def get_comic_info(cls, soup, link):
1780
        """Get information about a particular comics."""
1781
        title = soup.find('h2', class_='post-title').string
1782
        imgs = soup.find("div", id="comic").find_all("img")
1783
        assert all(i['alt'] == i['title'] for i in imgs)
1784
        alt = imgs[0]['alt']
1785
        return {
1786
            'img': [i['src'] for i in imgs],
1787
            'title': title,
1788
            'alt': alt,
1789
        }
1790
1791
1792
class MouseBearComedy(GenericComicNotWorking):  # Website has changed
1793
    """Class to retrieve Mouse Bear Comedy comics."""
1794
    # Also on http://mousebearcomedy.tumblr.com
1795
    name = 'mousebear'
1796
    long_name = 'Mouse Bear Comedy'
1797
    url = 'http://www.mousebearcomedy.com'
1798
    get_first_comic_link = get_a_navi_navifirst
1799
    get_navi_link = get_a_navi_comicnavnext_navinext
1800
1801
    @classmethod
1802
    def get_comic_info(cls, soup, link):
1803
        """Get information about a particular comics."""
1804
        title = soup.find('h2', class_='post-title').string
1805
        author = soup.find("span", class_="post-author").find("a").string
1806
        date_str = soup.find("span", class_="post-date").string
1807
        day = string_to_date(date_str, '%B %d, %Y')
1808
        imgs = soup.find("div", id="comic").find_all("img")
1809
        assert all(i['alt'] == i['title'] == title for i in imgs)
1810
        return {
1811
            'day': day.day,
1812
            'month': day.month,
1813
            'year': day.year,
1814
            'img': [i['src'] for i in imgs],
1815
            'title': title,
1816
            'author': author,
1817
        }
1818
1819
1820 View Code Duplication
class BigFootJustice(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
1821
    """Class to retrieve Big Foot Justice comics."""
1822
    # Also on http://tapastic.com/series/bigfoot-justice
1823
    name = 'bigfoot'
1824
    long_name = 'Big Foot Justice'
1825
    url = 'http://bigfootjustice.com'
1826
    get_first_comic_link = get_a_navi_navifirst
1827
    get_navi_link = get_a_navi_comicnavnext_navinext
1828
1829
    @classmethod
1830
    def get_comic_info(cls, soup, link):
1831
        """Get information about a particular comics."""
1832
        imgs = soup.find('div', id='comic').find_all('img')
1833
        assert all(i['title'] == i['alt'] for i in imgs)
1834
        title = ' '.join(i['title'] for i in imgs)
1835
        return {
1836
            'img': [i['src'] for i in imgs],
1837
            'title': title,
1838
        }
1839
1840
1841
class RespawnComic(GenericNavigableComic):
1842
    """Class to retrieve Respawn Comic."""
1843
    # Also on https://respawncomic.tumblr.com
1844
    name = 'respawn'
1845
    long_name = 'Respawn Comic'
1846
    url = 'http://respawncomic.com '
1847
    _categories = ('RESPAWN', )
1848
    get_navi_link = get_a_rel_next
1849
    get_first_comic_link = simulate_first_link
1850
    first_url = 'http://respawncomic.com/comic/c0001/'
1851
1852
    @classmethod
1853
    def get_comic_info(cls, soup, link):
1854
        """Get information about a particular comics."""
1855
        title = soup.find('meta', property='og:title')['content']
1856
        author = soup.find('meta', attrs={'name': 'shareaholic:article_author_name'})['content']
1857
        date_str = soup.find('meta', attrs={'name': 'shareaholic:article_published_time'})['content']
1858
        date_str = date_str[:10]
1859
        day = string_to_date(date_str, "%Y-%m-%d")
1860
        imgs = soup.find_all('meta', property='og:image')
1861
        skip_imgs = {
1862
            'http://respawncomic.com/wp-content/uploads/2016/03/site/HAROLD2.png',
1863
            'http://respawncomic.com/wp-content/uploads/2016/03/site/DEVA.png'
1864
        }
1865
        return {
1866
            'title': title,
1867
            'author': author,
1868
            'day': day.day,
1869
            'month': day.month,
1870
            'year': day.year,
1871
            'img': [i['content'] for i in imgs if i['content'] not in skip_imgs],
1872
        }
1873
1874
1875
class SafelyEndangered(GenericNavigableComic):
1876
    """Class to retrieve Safely Endangered comics."""
1877
    # Also on http://tumblr.safelyendangered.com
1878
    name = 'endangered'
1879
    long_name = 'Safely Endangered'
1880
    url = 'http://www.safelyendangered.com'
1881
    get_navi_link = get_link_rel_next
1882
    get_first_comic_link = simulate_first_link
1883
    first_url = 'http://www.safelyendangered.com/comic/ignored/'
1884
1885
    @classmethod
1886
    def get_comic_info(cls, soup, link):
1887
        """Get information about a particular comics."""
1888
        title = soup.find('h2', class_='post-title').string
1889
        date_str = soup.find('span', class_='post-date').string
1890
        day = string_to_date(date_str, '%B %d, %Y')
1891
        imgs = soup.find('div', id='comic').find_all('img')
1892
        alt = imgs[0]['alt']
1893
        assert all(i['alt'] == i['title'] for i in imgs)
1894
        return {
1895
            'day': day.day,
1896
            'month': day.month,
1897
            'year': day.year,
1898
            'img': [i['src'] for i in imgs],
1899
            'title': title,
1900
            'alt': alt,
1901
        }
1902
1903
1904
class PicturesInBoxes(GenericNavigableComic):
1905
    """Class to retrieve Pictures In Boxes comics."""
1906
    # Also on https://picturesinboxescomic.tumblr.com
1907
    name = 'picturesinboxes'
1908
    long_name = 'Pictures in Boxes'
1909
    url = 'http://www.picturesinboxes.com'
1910
    get_navi_link = get_a_navi_navinext
1911
    get_first_comic_link = simulate_first_link
1912
    first_url = 'http://www.picturesinboxes.com/2013/10/26/tetris/'
1913
1914
    @classmethod
1915
    def get_comic_info(cls, soup, link):
1916
        """Get information about a particular comics."""
1917
        title = soup.find('h2', class_='post-title').string
1918
        author = soup.find("span", class_="post-author").find("a").string
1919
        date_str = soup.find('span', class_='post-date').string
1920
        day = string_to_date(date_str, '%B %d, %Y')
1921
        imgs = soup.find('div', class_='comicpane').find_all('img')
1922
        assert imgs
1923
        assert all(i['title'] == i['alt'] == title for i in imgs)
1924
        return {
1925
            'day': day.day,
1926
            'month': day.month,
1927
            'year': day.year,
1928
            'img': [i['src'] for i in imgs],
1929
            'title': title,
1930
            'author': author,
1931
        }
1932
1933
1934
class Penmen(GenericComicNotWorking, GenericNavigableComic):
1935
    """Class to retrieve Penmen comics."""
1936
    name = 'penmen'
1937
    long_name = 'Penmen'
1938
    url = 'http://penmen.com'
1939
    get_navi_link = get_link_rel_next
1940
    get_first_comic_link = simulate_first_link
1941
    first_url = 'http://penmen.com/index.php/2016/09/12/penmen-announces-grin-big-brand-clothing/'
1942
1943
    @classmethod
1944
    def get_comic_info(cls, soup, link):
1945
        """Get information about a particular comics."""
1946
        title = soup.find('title').string
1947
        imgs = soup.find('div', class_='entry-content').find_all('img')
1948
        short_url = soup.find('link', rel='shortlink')['href']
1949
        tags = ' '.join(t.string for t in soup.find_all('a', rel='tag'))
1950
        date_str = soup.find('time')['datetime'][:10]
1951
        day = string_to_date(date_str, "%Y-%m-%d")
1952
        return {
1953
            'title': title,
1954
            'short_url': short_url,
1955
            'img': [i['src'] for i in imgs],
1956
            'tags': tags,
1957
            'month': day.month,
1958
            'year': day.year,
1959
            'day': day.day,
1960
        }
1961
1962
1963
class TheDoghouseDiaries(GenericDeletedComic, GenericNavigableComic):
1964
    """Class to retrieve The Dog House Diaries comics."""
1965
    name = 'doghouse'
1966
    long_name = 'The Dog House Diaries'
1967
    url = 'http://thedoghousediaries.com'
1968
1969
    @classmethod
1970
    def get_first_comic_link(cls):
1971
        """Get link to first comics."""
1972
        return get_soup_at_url(cls.url).find('a', id='firstlink')
1973
1974
    @classmethod
1975
    def get_navi_link(cls, last_soup, next_):
1976
        """Get link to next or previous comic."""
1977
        return last_soup.find('a', id='nextlink' if next_ else 'previouslink')
1978
1979
    @classmethod
1980
    def get_comic_info(cls, soup, link):
1981
        """Get information about a particular comics."""
1982
        comic_img_re = re.compile('^dhdcomics/.*')
1983
        img = soup.find('img', src=comic_img_re)
1984
        comic_url = cls.get_url_from_link(link)
1985
        return {
1986
            'title': soup.find('h2', id='titleheader').string,
1987
            'title2': soup.find('div', id='subtext').string,
1988
            'alt': img.get('title'),
1989
            'img': [urljoin_wrapper(comic_url, img['src'].strip())],
1990
            'num': int(comic_url.split('/')[-1]),
1991
        }
1992
1993
1994
class InvisibleBread(GenericListableComic):
1995
    """Class to retrieve Invisible Bread comics."""
1996
    # Also on http://www.gocomics.com/invisible-bread
1997
    name = 'invisiblebread'
1998
    long_name = 'Invisible Bread'
1999
    url = 'http://invisiblebread.com'
2000
2001
    @classmethod
2002
    def get_archive_elements(cls):
2003
        archive_url = urljoin_wrapper(cls.url, 'archives/')
2004
        return reversed(get_soup_at_url(archive_url).find_all('td', class_='archive-title'))
2005
2006
    @classmethod
2007
    def get_url_from_archive_element(cls, td):
2008
        return td.find('a')['href']
2009
2010
    @classmethod
2011
    def get_comic_info(cls, soup, td):
2012
        """Get information about a particular comics."""
2013
        url = cls.get_url_from_archive_element(td)
2014
        title = td.find('a').string
2015
        month_and_day = td.previous_sibling.string
2016
        link_re = re.compile('^%s/([0-9]+)/' % cls.url)
2017
        year = link_re.match(url).groups()[0]
2018
        date_str = month_and_day + ' ' + year
2019
        day = string_to_date(date_str, '%b %d %Y')
2020
        imgs = [soup.find('div', id='comic').find('img')]
2021
        assert len(imgs) == 1, imgs
2022
        assert all(i['title'] == i['alt'] == title for i in imgs)
2023
        return {
2024
            'month': day.month,
2025
            'year': day.year,
2026
            'day': day.day,
2027
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
2028
            'title': title,
2029
        }
2030
2031
2032
class DiscoBleach(GenericDeletedComic):
2033
    """Class to retrieve Disco Bleach Comics."""
2034
    name = 'discobleach'
2035
    long_name = 'Disco Bleach'
2036
    url = 'http://discobleach.com'
2037
2038
2039
class TubeyToons(GenericDeletedComic):
2040
    """Class to retrieve TubeyToons comics."""
2041
    # Also on http://tapastic.com/series/Tubey-Toons
2042
    # Also on https://tubeytoons.tumblr.com
2043
    name = 'tubeytoons'
2044
    long_name = 'Tubey Toons'
2045
    url = 'http://tubeytoons.com'
2046
    _categories = ('TUNEYTOONS', )
2047
2048
2049
class CompletelySeriousComics(GenericNavigableComic):
2050
    """Class to retrieve Completely Serious comics."""
2051
    name = 'completelyserious'
2052
    long_name = 'Completely Serious Comics'
2053
    url = 'http://completelyseriouscomics.com'
2054
    get_first_comic_link = get_a_navi_navifirst
2055
    get_navi_link = get_a_navi_navinext
2056
2057
    @classmethod
2058
    def get_comic_info(cls, soup, link):
2059
        """Get information about a particular comics."""
2060
        title = soup.find('h2', class_='post-title').string
2061
        author = soup.find('span', class_='post-author').contents[1].string
2062
        date_str = soup.find('span', class_='post-date').string
2063
        day = string_to_date(date_str, '%B %d, %Y')
2064
        imgs = soup.find('div', class_='comicpane').find_all('img')
2065
        assert imgs
2066
        alt = imgs[0]['title']
2067
        assert all(i['title'] == i['alt'] == alt for i in imgs)
2068
        return {
2069
            'month': day.month,
2070
            'year': day.year,
2071
            'day': day.day,
2072
            'img': [i['src'] for i in imgs],
2073
            'title': title,
2074
            'alt': alt,
2075
            'author': author,
2076
        }
2077
2078
2079
class PoorlyDrawnLines(GenericListableComic):
2080
    """Class to retrieve Poorly Drawn Lines comics."""
2081
    # Also on http://pdlcomics.tumblr.com
2082
    name = 'poorlydrawn'
2083
    long_name = 'Poorly Drawn Lines'
2084
    url = 'https://www.poorlydrawnlines.com'
2085
    _categories = ('POORLYDRAWN', )
2086
    get_url_from_archive_element = get_href
2087
2088
    @classmethod
2089
    def get_comic_info(cls, soup, link):
2090
        """Get information about a particular comics."""
2091
        imgs = soup.find('div', class_='post').find_all('img')
2092
        assert len(imgs) <= 1, imgs
2093
        return {
2094
            'img': [i['src'] for i in imgs],
2095
            'title': imgs[0].get('title', "") if imgs else "",
2096
        }
2097
2098
    @classmethod
2099
    def get_archive_elements(cls):
2100
        archive_url = urljoin_wrapper(cls.url, 'archive')
2101
        url_re = re.compile('^%s/comic/.' % cls.url)
2102
        return reversed(get_soup_at_url(archive_url).find_all('a', href=url_re))
2103
2104
2105
class LoadingComics(GenericNavigableComic):
2106
    """Class to retrieve Loading Artist comics."""
2107
    name = 'loadingartist'
2108
    long_name = 'Loading Artist'
2109
    url = 'http://www.loadingartist.com/latest'
2110
2111
    @classmethod
2112
    def get_first_comic_link(cls):
2113
        """Get link to first comics."""
2114
        return get_soup_at_url(cls.url).find('a', title="First")
2115
2116
    @classmethod
2117
    def get_navi_link(cls, last_soup, next_):
2118
        """Get link to next or previous comic."""
2119
        return last_soup.find('a', title='Next' if next_ else 'Previous')
2120
2121
    @classmethod
2122
    def get_comic_info(cls, soup, link):
2123
        """Get information about a particular comics."""
2124
        title = soup.find('h1').string
2125
        date_str = soup.find('span', class_='date').string.strip()
2126
        day = string_to_date(date_str, "%B %d, %Y")
2127
        imgs = soup.find('div', class_='comic').find_all('img', alt='', title='')
2128
        return {
2129
            'title': title,
2130
            'img': [i['src'] for i in imgs],
2131
            'month': day.month,
2132
            'year': day.year,
2133
            'day': day.day,
2134
        }
2135
2136
2137
class ChuckleADuck(GenericNavigableComic):
2138
    """Class to retrieve Chuckle-A-Duck comics."""
2139
    name = 'chuckleaduck'
2140
    long_name = 'Chuckle-A-duck'
2141
    url = 'http://chuckleaduck.com'
2142
    get_first_comic_link = get_div_navfirst_a
2143
    get_navi_link = get_link_rel_next
2144
2145
    @classmethod
2146
    def get_comic_info(cls, soup, link):
2147
        """Get information about a particular comics."""
2148
        date_str = soup.find('span', class_='post-date').string
2149
        day = string_to_date(remove_st_nd_rd_th_from_date(date_str), "%B %d, %Y")
2150
        author = soup.find('span', class_='post-author').string
2151
        div = soup.find('div', id='comic')
2152
        imgs = div.find_all('img') if div else []
2153
        title = imgs[0]['title'] if imgs else ""
2154
        assert all(i['title'] == i['alt'] == title for i in imgs)
2155
        return {
2156
            'month': day.month,
2157
            'year': day.year,
2158
            'day': day.day,
2159
            'img': [i['src'] for i in imgs],
2160
            'title': title,
2161
            'author': author,
2162
        }
2163
2164
2165
class DepressedAlien(GenericNavigableComic):
2166
    """Class to retrieve Depressed Alien Comics."""
2167
    name = 'depressedalien'
2168
    long_name = 'Depressed Alien'
2169
    url = 'http://depressedalien.com'
2170
    get_url_from_link = join_cls_url_to_href
2171
2172
    @classmethod
2173
    def get_first_comic_link(cls):
2174
        """Get link to first comics."""
2175
        return get_soup_at_url(cls.url).find('img', attrs={'name': 'beginArrow'}).parent
2176
2177
    @classmethod
2178
    def get_navi_link(cls, last_soup, next_):
2179
        """Get link to next or previous comic."""
2180
        return last_soup.find('img', attrs={'name': 'rightArrow' if next_ else 'leftArrow'}).parent
2181
2182
    @classmethod
2183
    def get_comic_info(cls, soup, link):
2184
        """Get information about a particular comics."""
2185
        title = soup.find('meta', attrs={'name': 'twitter:title'})['content']
2186
        imgs = soup.find_all('meta', property='og:image')
2187
        return {
2188
            'title': title,
2189
            'img': [i['content'] for i in imgs],
2190
        }
2191
2192
2193
class TurnOffUs(GenericListableComic):
2194
    """Class to retrieve TurnOffUs comics."""
2195
    name = 'turnoffus'
2196
    long_name = 'Turn Off Us'
2197
    url = 'http://turnoff.us'
2198
    get_url_from_archive_element = join_cls_url_to_href
2199
2200
    @classmethod
2201
    def get_archive_elements(cls):
2202
        archive_url = urljoin_wrapper(cls.url, 'all')
2203
        post_list = get_soup_at_url(archive_url).find('ul', class_='post-list')
2204
        return reversed(post_list.find_all('a', class_='post-link'))
2205
2206
    @classmethod
2207
    def get_comic_info(cls, soup, archive_elt):
2208
        """Get information about a particular comics."""
2209
        title = soup.find('meta', property='og:title')['content']
2210
        imgs = soup.find_all('meta', property='og:image')
2211
        return {
2212
            'title': title,
2213
            'img': [i['content'] for i in imgs],
2214
        }
2215
2216
2217
class ThingsInSquares(GenericListableComic):
2218
    """Class to retrieve Things In Squares comics."""
2219
    # This can be retrieved in other languages
2220
    # Also on https://tapastic.com/series/Things-in-Squares
2221
    name = 'squares'
2222
    long_name = 'Things in squares'
2223
    url = 'http://www.thingsinsquares.com'
2224
2225
    @classmethod
2226
    def get_comic_info(cls, soup, tr):
2227
        """Get information about a particular comics."""
2228
        _, td2, td3 = tr.find_all('td')
2229
        a = td2.find('a')
2230
        date_str = td3.string
2231
        day = string_to_date(date_str, "%m.%d.%y")
2232
        title = a.string
2233
        title2 = soup.find('meta', property='og:title')['content']
2234
        desc = soup.find('meta', property='og:description')
2235
        description = desc['content'] if desc else ''
2236
        tags = ' '.join(t['content'] for t in soup.find_all('meta', property='article:tag'))
2237
        imgs = soup.find_all('meta', property='og:image')
2238
        return {
2239
            'day': day.day,
2240
            'month': day.month,
2241
            'year': day.year,
2242
            'title': title,
2243
            'title2': title2,
2244
            'description': description,
2245
            'tags': tags,
2246
            'img': [i['content'] for i in imgs],
2247
        }
2248
2249
    @classmethod
2250
    def get_url_from_archive_element(cls, tr):
2251
        _, td2, __ = tr.find_all('td')
2252
        return td2.find('a')['href']
2253
2254
    @classmethod
2255
    def get_archive_elements(cls):
2256
        archive_url = urljoin_wrapper(cls.url, 'archive-2')
2257
        return reversed(get_soup_at_url(archive_url).find('tbody').find_all('tr'))
2258
2259
2260
class HappleTea(GenericNavigableComic):
2261
    """Class to retrieve Happle Tea Comics."""
2262
    name = 'happletea'
2263
    long_name = 'Happle Tea'
2264
    url = 'http://www.happletea.com'
2265
    get_first_comic_link = get_a_navi_navifirst
2266
    get_navi_link = get_link_rel_next
2267
2268
    @classmethod
2269
    def get_comic_info(cls, soup, link):
2270
        """Get information about a particular comics."""
2271
        imgs = soup.find('div', id='comic').find_all('img')
2272
        post = soup.find('div', class_='post-content')
2273
        title = post.find('h2', class_='post-title').string
2274
        author = post.find('a', rel='author').string
2275
        date_str = post.find('span', class_='post-date').string
2276
        day = string_to_date(date_str, "%B %d, %Y")
2277
        assert all(i['alt'] == i['title'] for i in imgs)
2278
        return {
2279
            'title': title,
2280
            'img': [i['src'] for i in imgs],
2281
            'alt': ''.join(i['alt'] for i in imgs),
2282
            'month': day.month,
2283
            'year': day.year,
2284
            'day': day.day,
2285
            'author': author,
2286
        }
2287
2288
2289
class RockPaperScissors(GenericNavigableComic):
2290
    """Class to retrieve Rock Paper Scissors comics."""
2291
    name = 'rps'
2292
    long_name = 'Rock Paper Scissors'
2293
    url = 'http://rps-comics.com'
2294
    get_first_comic_link = get_a_navi_navifirst
2295
    get_navi_link = get_link_rel_next
2296
2297
    @classmethod
2298
    def get_comic_info(cls, soup, link):
2299
        """Get information about a particular comics."""
2300
        title = soup.find('title').string
2301
        imgs = soup.find_all('meta', property='og:image')
2302
        short_url = soup.find('link', rel='shortlink')['href']
2303
        transcript = soup.find('div', id='transcript-content').string
2304
        return {
2305
            'title': title,
2306
            'transcript': transcript,
2307
            'short_url': short_url,
2308
            'img': [i['content'] for i in imgs],
2309
        }
2310
2311
2312
class FatAwesomeComics(GenericNavigableComic):
2313
    """Class to retrieve Fat Awesome Comics."""
2314
    # Also on http://fatawesomecomedy.tumblr.com
2315
    name = 'fatawesome'
2316
    long_name = 'Fat Awesome'
2317
    url = 'http://fatawesome.com/comics'
2318
    get_navi_link = get_a_rel_next
2319
    get_first_comic_link = simulate_first_link
2320
    first_url = 'http://fatawesome.com/shortbus/'
2321
2322
    @classmethod
2323
    def get_comic_info(cls, soup, link):
2324
        """Get information about a particular comics."""
2325
        title = soup.find('meta', attrs={'name': 'twitter:title'})['content']
2326
        description = soup.find('meta', attrs={'name': 'description'})['content']
2327
        tags_prop = soup.find('meta', property='article:tag')
2328
        tags = tags_prop['content'] if tags_prop else ""
2329
        date_str = soup.find('meta', property='article:published_time')['content'][:10]
2330
        day = string_to_date(date_str, "%Y-%m-%d")
2331
        imgs = soup.find_all('img', attrs={'data-recalc-dims': "1"})
2332
        assert len(imgs) == 1, imgs
2333
        return {
2334
            'title': title,
2335
            'description': description,
2336
            'tags': tags,
2337
            'alt': "".join(i['alt'] for i in imgs),
2338
            'img': [i['src'].rsplit('?', 1)[0] for i in imgs],
2339
            'month': day.month,
2340
            'year': day.year,
2341
            'day': day.day,
2342
        }
2343
2344
2345
class JuliasDrawings(GenericListableComic):
2346
    """Class to retrieve Julia's Drawings."""
2347
    name = 'julia'
2348
    long_name = "Julia's Drawings"
2349
    url = 'https://drawings.jvns.ca'
2350
    get_url_from_archive_element = get_href
2351
2352
    @classmethod
2353
    def get_archive_elements(cls):
2354
        articles = get_soup_at_url(cls.url).find_all('article', class_='li post')
2355
        return [art.find('a') for art in reversed(articles)]
2356
2357
    @classmethod
2358
    def get_comic_info(cls, soup, archive_elt):
2359
        """Get information about a particular comics."""
2360
        date_str = soup.find('meta', property='og:article:published_time')['content'][:10]
2361
        day = string_to_date(date_str, "%Y-%m-%d")
2362
        title = soup.find('h3', class_='p-post-title').string
2363
        imgs = soup.find('section', class_='post-content').find_all('img')
2364
        return {
2365
            'title': title,
2366
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
2367
            'month': day.month,
2368
            'year': day.year,
2369
            'day': day.day,
2370
        }
2371
2372
2373
class AnythingComic(GenericListableComic):
2374
    """Class to retrieve Anything Comics."""
2375
    # Also on http://tapastic.com/series/anything
2376
    name = 'anythingcomic'
2377
    long_name = 'Anything Comic'
2378
    url = 'http://www.anythingcomic.com'
2379
2380
    @classmethod
2381
    def get_archive_elements(cls):
2382
        archive_url = urljoin_wrapper(cls.url, 'archive/')
2383
        # The first 2 <tr>'s do not correspond to comics
2384
        return get_soup_at_url(archive_url).find('table', id='chapter_table').find_all('tr')[2:]
2385
2386
    @classmethod
2387
    def get_url_from_archive_element(cls, tr):
2388
        """Get url corresponding to an archive element."""
2389
        _, td_comic, td_date, _ = tr.find_all('td')
2390
        link = td_comic.find('a')
2391
        return urljoin_wrapper(cls.url, link['href'])
2392
2393
    @classmethod
2394
    def get_comic_info(cls, soup, tr):
2395
        """Get information about a particular comics."""
2396
        td_num, td_comic, td_date, _ = tr.find_all('td')
2397
        num = int(td_num.string)
2398
        link = td_comic.find('a')
2399
        title = link.string
2400
        imgs = soup.find_all('img', id='comic_image')
2401
        date_str = td_date.string
2402
        day = string_to_date(remove_st_nd_rd_th_from_date(date_str), "%B %d, %Y, %I:%M %p")
2403
        assert len(imgs) == 1, imgs
2404
        assert all(i.get('alt') == i.get('title') for i in imgs)
2405
        return {
2406
            'num': num,
2407
            'title': title,
2408
            'alt': imgs[0].get('alt', ''),
2409
            'img': [i['src'] for i in imgs],
2410
            'month': day.month,
2411
            'year': day.year,
2412
            'day': day.day,
2413
        }
2414
2415
2416
class LonnieMillsap(GenericNavigableComic):
2417
    """Class to retrieve Lonnie Millsap's comics."""
2418
    name = 'millsap'
2419
    long_name = 'Lonnie Millsap'
2420
    url = 'http://www.lonniemillsap.com'
2421
    get_navi_link = get_link_rel_next
2422
    get_first_comic_link = simulate_first_link
2423
    first_url = 'http://www.lonniemillsap.com/?p=42'
2424
2425
    @classmethod
2426
    def get_comic_info(cls, soup, link):
2427
        """Get information about a particular comics."""
2428
        title = soup.find('h2', class_='post-title').string
2429
        post = soup.find('div', class_='post-content')
2430
        author = post.find("span", class_="post-author").find("a").string
2431
        date_str = post.find("span", class_="post-date").string
2432
        day = string_to_date(date_str, "%B %d, %Y")
2433
        imgs = post.find("div", class_="entry").find_all("img")
2434
        return {
2435
            'title': title,
2436
            'author': author,
2437
            'img': [i['src'] for i in imgs],
2438
            'month': day.month,
2439
            'year': day.year,
2440
            'day': day.day,
2441
        }
2442
2443
2444
class LinsEditions(GenericNavigableComic):
2445
    """Class to retrieve L.I.N.S. Editions comics."""
2446
    # Also on https://linscomics.tumblr.com
2447
    # Now on https://warandpeas.com
2448
    name = 'lins'
2449
    long_name = 'L.I.N.S. Editions'
2450
    url = 'https://linsedition.com'
2451
    _categories = ('LINS', )
2452
    get_navi_link = get_link_rel_next
2453
    get_first_comic_link = simulate_first_link
2454
    first_url = 'https://linsedition.com/2011/09/07/l-i-n-s/'
2455
2456
    @classmethod
2457
    def get_comic_info(cls, soup, link):
2458
        """Get information about a particular comics."""
2459
        title = soup.find('meta', property='og:title')['content']
2460
        imgs = soup.find_all('meta', property='og:image')
2461
        date_str = soup.find('meta', property='article:published_time')['content'][:10]
2462
        day = string_to_date(date_str, "%Y-%m-%d")
2463
        return {
2464
            'title': title,
2465
            'img': [i['content'] for i in imgs],
2466
            'month': day.month,
2467
            'year': day.year,
2468
            'day': day.day,
2469
        }
2470
2471
2472
class ThorsThundershack(GenericNavigableComic):
2473
    """Class to retrieve Thor's Thundershack comics."""
2474
    # Also on http://tapastic.com/series/Thors-Thundershac
2475
    name = 'thor'
2476
    long_name = 'Thor\'s Thundershack'
2477
    url = 'http://www.thorsthundershack.com'
2478
    _categories = ('THOR', )
2479
    get_url_from_link = join_cls_url_to_href
2480
2481
    @classmethod
2482
    def get_first_comic_link(cls):
2483
        """Get link to first comics."""
2484
        return get_soup_at_url(cls.url).find('a', class_='first navlink')
2485
2486
    @classmethod
2487
    def get_navi_link(cls, last_soup, next_):
2488
        """Get link to next or previous comic."""
2489
        for link in last_soup.find_all('a', rel='next' if next_ else 'prev'):
2490
            if link['href'] != '/comic':
2491
                return link
2492
        return None
2493
2494
    @classmethod
2495
    def get_comic_info(cls, soup, link):
2496
        """Get information about a particular comics."""
2497
        title = soup.find('meta', attrs={'name': 'description'})["content"]
2498
        description = soup.find('div', itemprop='articleBody').text
2499
        author = soup.find('span', itemprop='author copyrightHolder').string
2500
        imgs = soup.find_all('img', itemprop='image')
2501
        assert all(i['title'] == i['alt'] for i in imgs)
2502
        alt = imgs[0]['alt'] if imgs else ""
2503
        date_str = soup.find('time', itemprop='datePublished')["datetime"]
2504
        day = string_to_date(date_str, "%Y-%m-%d %H:%M:%S")
2505
        return {
2506
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
2507
            'month': day.month,
2508
            'year': day.year,
2509
            'day': day.day,
2510
            'author': author,
2511
            'title': title,
2512
            'alt': alt,
2513
            'description': description,
2514
        }
2515
2516
2517
class GerbilWithAJetpack(GenericNavigableComic):
2518
    """Class to retrieve GerbilWithAJetpack comics."""
2519
    name = 'gerbil'
2520
    long_name = 'Gerbil With A Jetpack'
2521
    url = 'http://gerbilwithajetpack.com'
2522
    get_first_comic_link = get_a_navi_navifirst
2523
    get_navi_link = get_a_rel_next
2524
2525
    @classmethod
2526
    def get_comic_info(cls, soup, link):
2527
        """Get information about a particular comics."""
2528
        title = soup.find('h2', class_='post-title').string
2529
        author = soup.find("span", class_="post-author").find("a").string
2530
        date_str = soup.find("span", class_="post-date").string
2531
        day = string_to_date(date_str, "%B %d, %Y")
2532
        imgs = soup.find("div", id="comic").find_all("img")
2533
        alt = imgs[0]['alt']
2534
        assert all(i['alt'] == i['title'] == alt for i in imgs)
2535
        return {
2536
            'img': [i['src'] for i in imgs],
2537
            'title': title,
2538
            'alt': alt,
2539
            'author': author,
2540
            'day': day.day,
2541
            'month': day.month,
2542
            'year': day.year
2543
        }
2544
2545
2546
class EveryDayBlues(GenericDeletedComic, GenericNavigableComic):
2547
    """Class to retrieve EveryDayBlues Comics."""
2548
    name = "blues"
2549
    long_name = "Every Day Blues"
2550
    url = "http://everydayblues.net"
2551
    get_first_comic_link = get_a_navi_navifirst
2552
    get_navi_link = get_link_rel_next
2553
2554
    @classmethod
2555
    def get_comic_info(cls, soup, link):
2556
        """Get information about a particular comics."""
2557
        title = soup.find("h2", class_="post-title").string
2558
        author = soup.find("span", class_="post-author").find("a").string
2559
        date_str = soup.find("span", class_="post-date").string
2560
        day = string_to_date(date_str, "%d. %B %Y", "de_DE.utf8")
2561
        imgs = soup.find("div", id="comic").find_all("img")
2562
        assert all(i['alt'] == i['title'] == title for i in imgs)
2563
        assert len(imgs) <= 1, imgs
2564
        return {
2565
            'img': [i['src'] for i in imgs],
2566
            'title': title,
2567
            'author': author,
2568
            'day': day.day,
2569
            'month': day.month,
2570
            'year': day.year
2571
        }
2572
2573
2574
class BiterComics(GenericNavigableComic):
2575
    """Class to retrieve Biter Comics."""
2576
    name = "biter"
2577
    long_name = "Biter Comics"
2578
    url = "http://www.bitercomics.com"
2579
    get_first_comic_link = get_a_navi_navifirst
2580
    get_navi_link = get_link_rel_next
2581
2582
    @classmethod
2583
    def get_comic_info(cls, soup, link):
2584
        """Get information about a particular comics."""
2585
        title = soup.find("h1", class_="entry-title").string
2586
        author = soup.find("span", class_="author vcard").find("a").string
2587
        date_str = soup.find("span", class_="entry-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(i['alt'] == i['title'] for i in imgs)
2591
        assert len(imgs) == 1, imgs
2592
        alt = imgs[0]['alt']
2593
        return {
2594
            'img': [i['src'] for i in imgs],
2595
            'title': title,
2596
            'alt': alt,
2597
            'author': author,
2598
            'day': day.day,
2599
            'month': day.month,
2600
            'year': day.year
2601
        }
2602
2603
2604
class TheAwkwardYeti(GenericNavigableComic):
2605
    """Class to retrieve The Awkward Yeti comics."""
2606
    # Also on http://www.gocomics.com/the-awkward-yeti
2607
    # Also on http://larstheyeti.tumblr.com
2608
    # Also on https://tapastic.com/series/TheAwkwardYeti
2609
    name = 'yeti'
2610
    long_name = 'The Awkward Yeti'
2611
    url = 'http://theawkwardyeti.com'
2612
    _categories = ('YETI', )
2613
    get_first_comic_link = get_a_navi_navifirst
2614
    get_navi_link = get_link_rel_next
2615
2616
    @classmethod
2617
    def get_comic_info(cls, soup, link):
2618
        """Get information about a particular comics."""
2619
        title = soup.find('h2', class_='post-title').string
2620
        date_str = soup.find("span", class_="post-date").string
2621
        day = string_to_date(date_str, "%B %d, %Y")
2622
        imgs = soup.find("div", id="comic").find_all("img")
2623
        assert all(idx > 0 or i['alt'] == i['title'] for idx, i in enumerate(imgs))
2624
        return {
2625
            'img': [i['src'] for i in imgs],
2626
            'title': title,
2627
            'day': day.day,
2628
            'month': day.month,
2629
            'year': day.year
2630
        }
2631
2632
2633
class PleasantThoughts(GenericNavigableComic):
2634
    """Class to retrieve Pleasant Thoughts comics."""
2635
    name = 'pleasant'
2636
    long_name = 'Pleasant Thoughts'
2637
    url = 'http://pleasant-thoughts.com'
2638
    get_first_comic_link = get_a_navi_navifirst
2639
    get_navi_link = get_link_rel_next
2640
2641
    @classmethod
2642
    def get_comic_info(cls, soup, link):
2643
        """Get information about a particular comics."""
2644
        post = soup.find('div', class_='post-content')
2645
        title = post.find('h2', class_='post-title').string
2646
        imgs = post.find("div", class_="entry").find_all("img")
2647
        return {
2648
            'title': title,
2649
            'img': [i['src'] for i in imgs],
2650
        }
2651
2652
2653
class MisterAndMe(GenericNavigableComic):
2654
    """Class to retrieve Mister & Me Comics."""
2655
    # Also on http://www.gocomics.com/mister-and-me
2656
    # Also on https://tapastic.com/series/Mister-and-Me
2657
    name = 'mister'
2658
    long_name = 'Mister & Me'
2659
    url = 'http://www.mister-and-me.com'
2660
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
2661
    get_navi_link = get_link_rel_next
2662
2663
    @classmethod
2664
    def get_comic_info(cls, soup, link):
2665
        """Get information about a particular comics."""
2666
        title = soup.find('h2', class_='post-title').string
2667
        author = soup.find("span", class_="post-author").find("a").string
2668
        date_str = soup.find("span", class_="post-date").string
2669
        day = string_to_date(date_str, "%B %d, %Y")
2670
        imgs = soup.find("div", id="comic").find_all("img")
2671
        assert all(i['alt'] == i['title'] for i in imgs)
2672
        assert len(imgs) <= 1, imgs
2673
        alt = imgs[0]['alt'] if imgs else ""
2674
        return {
2675
            'img': [i['src'] for i in imgs],
2676
            'title': title,
2677
            'alt': alt,
2678
            'author': author,
2679
            'day': day.day,
2680
            'month': day.month,
2681
            'year': day.year
2682
        }
2683
2684
2685
class LastPlaceComics(GenericNavigableComic):
2686
    """Class to retrieve Last Place Comics."""
2687
    name = 'lastplace'
2688
    long_name = 'Last Place Comics'
2689
    url = "http://lastplacecomics.com"
2690
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
2691
    get_navi_link = get_link_rel_next
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
        assert len(imgs) <= 1, imgs
2703
        alt = imgs[0]['alt'] if imgs else ""
2704
        return {
2705
            'img': [i['src'] for i in imgs],
2706
            'title': title,
2707
            'alt': alt,
2708
            'author': author,
2709
            'day': day.day,
2710
            'month': day.month,
2711
            'year': day.year
2712
        }
2713
2714
2715
class TalesOfAbsurdity(GenericNavigableComic):
2716
    """Class to retrieve Tales Of Absurdity comics."""
2717
    # Also on http://tapastic.com/series/Tales-Of-Absurdity
2718
    # Also on http://talesofabsurdity.tumblr.com
2719
    name = 'absurdity'
2720
    long_name = 'Tales of Absurdity'
2721
    url = 'http://talesofabsurdity.com'
2722
    _categories = ('ABSURDITY', )
2723
    get_first_comic_link = get_a_navi_navifirst
2724
    get_navi_link = get_a_navi_comicnavnext_navinext
2725
2726
    @classmethod
2727
    def get_comic_info(cls, soup, link):
2728
        """Get information about a particular comics."""
2729
        title = soup.find('h2', class_='post-title').string
2730
        author = soup.find("span", class_="post-author").find("a").string
2731
        date_str = soup.find("span", class_="post-date").string
2732
        day = string_to_date(date_str, "%B %d, %Y")
2733
        imgs = soup.find("div", id="comic").find_all("img")
2734
        assert all(i['alt'] == i['title'] for i in imgs)
2735
        alt = imgs[0]['alt'] if imgs else ""
2736
        return {
2737
            'img': [i['src'] for i in imgs],
2738
            'title': title,
2739
            'alt': alt,
2740
            'author': author,
2741
            'day': day.day,
2742
            'month': day.month,
2743
            'year': day.year
2744
        }
2745
2746
2747
class EndlessOrigami(GenericComicNotWorking, GenericNavigableComic):  # Nav not working
2748
    """Class to retrieve Endless Origami Comics."""
2749
    name = "origami"
2750
    long_name = "Endless Origami"
2751
    url = "http://endlessorigami.com"
2752
    get_first_comic_link = get_a_navi_navifirst
2753
    get_navi_link = get_link_rel_next
2754
2755
    @classmethod
2756
    def get_comic_info(cls, soup, link):
2757
        """Get information about a particular comics."""
2758
        title = soup.find('h2', class_='post-title').string
2759
        author = soup.find("span", class_="post-author").find("a").string
2760
        date_str = soup.find("span", class_="post-date").string
2761
        day = string_to_date(date_str, "%B %d, %Y")
2762
        imgs = soup.find("div", id="comic").find_all("img")
2763
        assert all(i['alt'] == i['title'] for i in imgs)
2764
        alt = imgs[0]['alt'] if imgs else ""
2765
        return {
2766
            'img': [i['src'] for i in imgs],
2767
            'title': title,
2768
            'alt': alt,
2769
            'author': author,
2770
            'day': day.day,
2771
            'month': day.month,
2772
            'year': day.year
2773
        }
2774
2775
2776
class PlanC(GenericNavigableComic):
2777
    """Class to retrieve Plan C comics."""
2778
    name = 'planc'
2779
    long_name = 'Plan C'
2780
    url = 'http://www.plancomic.com'
2781
    get_first_comic_link = get_a_navi_navifirst
2782
    get_navi_link = get_a_navi_comicnavnext_navinext
2783
2784
    @classmethod
2785
    def get_comic_info(cls, soup, link):
2786
        """Get information about a particular comics."""
2787
        title = soup.find('h2', class_='post-title').string
2788
        date_str = soup.find("span", class_="post-date").string
2789
        day = string_to_date(date_str, "%B %d, %Y")
2790
        imgs = soup.find('div', id='comic').find_all('img')
2791
        return {
2792
            'title': title,
2793
            'img': [i['src'] for i in imgs],
2794
            'month': day.month,
2795
            'year': day.year,
2796
            'day': day.day,
2797
        }
2798
2799
2800 View Code Duplication
class BuniComic(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
2801
    """Class to retrieve Buni Comics."""
2802
    name = 'buni'
2803
    long_name = 'BuniComics'
2804
    url = 'http://www.bunicomic.com'
2805
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
2806
    get_navi_link = get_link_rel_next
2807
2808
    @classmethod
2809
    def get_comic_info(cls, soup, link):
2810
        """Get information about a particular comics."""
2811
        imgs = soup.find('div', id='comic').find_all('img')
2812
        assert all(i['alt'] == i['title'] for i in imgs)
2813
        assert len(imgs) == 1, imgs
2814
        return {
2815
            'img': [i['src'] for i in imgs],
2816
            'title': imgs[0]['title'],
2817
        }
2818
2819
2820
class GenericCommitStrip(GenericNavigableComic):
2821
    """Generic class to retrieve Commit Strips in different languages."""
2822
    get_navi_link = get_a_rel_next
2823
    get_first_comic_link = simulate_first_link
2824
    first_url = NotImplemented
2825
2826
    @classmethod
2827
    def get_comic_info(cls, soup, link):
2828
        """Get information about a particular comics."""
2829
        desc = soup.find('meta', property='og:description')['content']
2830
        title = soup.find('meta', property='og:title')['content']
2831
        imgs = soup.find('div', class_='entry-content').find_all('img')
2832
        title2 = ' '.join(i.get('title', '') for i in imgs)
2833
        return {
2834
            'title': title,
2835
            'title2': title2,
2836
            'description': desc,
2837
            'img': [urljoin_wrapper(cls.url, convert_iri_to_plain_ascii_uri(i['src'])) for i in imgs],
2838
        }
2839
2840
2841
class CommitStripFr(GenericCommitStrip):
2842
    """Class to retrieve Commit Strips in French."""
2843
    name = 'commit_fr'
2844
    long_name = 'Commit Strip (Fr)'
2845
    url = 'http://www.commitstrip.com/fr'
2846
    _categories = ('FRANCAIS', )
2847
    first_url = 'http://www.commitstrip.com/fr/2012/02/22/interview/'
2848
2849
2850
class CommitStripEn(GenericCommitStrip):
2851
    """Class to retrieve Commit Strips in English."""
2852
    name = 'commit_en'
2853
    long_name = 'Commit Strip (En)'
2854
    url = 'http://www.commitstrip.com/en'
2855
    first_url = 'http://www.commitstrip.com/en/2012/02/22/interview/'
2856
2857
2858
class GenericBoumerie(GenericNavigableComic):
2859
    """Generic class to retrieve Boumeries comics in different languages."""
2860
    # Also on http://boumeries.tumblr.com
2861
    get_first_comic_link = get_a_navi_navifirst
2862
    get_navi_link = get_link_rel_next
2863
    date_format = NotImplemented
2864
    lang = NotImplemented
2865
2866
    @classmethod
2867
    def get_comic_info(cls, soup, link):
2868
        """Get information about a particular comics."""
2869
        title = soup.find('h2', class_='post-title').string
2870
        short_url = soup.find('link', rel='shortlink')['href']
2871
        author = soup.find("span", class_="post-author").find("a").string
2872
        date_str = soup.find('span', class_='post-date').string
2873
        day = string_to_date(date_str, cls.date_format, cls.lang)
2874
        imgs = soup.find('div', id='comic').find_all('img')
2875
        assert all(i['alt'] == i['title'] for i in imgs)
2876
        return {
2877
            'short_url': short_url,
2878
            'img': [i['src'] for i in imgs],
2879
            'title': title,
2880
            'author': author,
2881
            'month': day.month,
2882
            'year': day.year,
2883
            'day': day.day,
2884
        }
2885
2886
2887
class BoumerieEn(GenericBoumerie):
2888
    """Class to retrieve Boumeries comics in English."""
2889
    name = 'boumeries_en'
2890
    long_name = 'Boumeries (En)'
2891
    url = 'http://comics.boumerie.com'
2892
    _categories = ('BOUMERIES', )
2893
    date_format = "%B %d, %Y"
2894
    lang = 'en_GB.UTF-8'
2895
2896
2897
class BoumerieFr(GenericBoumerie):
2898
    """Class to retrieve Boumeries comics in French."""
2899
    name = 'boumeries_fr'
2900
    long_name = 'Boumeries (Fr)'
2901
    url = 'http://bd.boumerie.com'
2902
    _categories = ('BOUMERIES', 'FRANCAIS')
2903
    date_format = "%B %d, %Y"  # Used to be "%A, %d %B %Y"
2904
    lang = "fr_FR.utf8"
2905
2906
2907
class UnearthedComics(GenericNavigableComic):
2908
    """Class to retrieve Unearthed comics."""
2909
    # Also on http://tapastic.com/series/UnearthedComics
2910
    # Also on https://unearthedcomics.tumblr.com
2911
    name = 'unearthed'
2912
    long_name = 'Unearthed Comics'
2913
    url = 'http://unearthedcomics.com'
2914
    _categories = ('UNEARTHED', )
2915
    get_navi_link = get_link_rel_next
2916
    get_first_comic_link = simulate_first_link
2917
    first_url = 'http://unearthedcomics.com/comics/world-with-turn-signals/'
2918
2919
    @classmethod
2920
    def get_comic_info(cls, soup, link):
2921
        """Get information about a particular comics."""
2922
        short_url = soup.find('link', rel='shortlink')['href']
2923
        title_elt = soup.find('h1') or soup.find('h2')
2924
        title = title_elt.string if title_elt else ""
2925
        desc = soup.find('meta', property='og:description')
2926
        date_str = soup.find('time', class_='published updated hidden')['datetime']
2927
        day = string_to_date(date_str, "%Y-%m-%d")
2928
        post = soup.find('div', class_="entry content entry-content type-portfolio")
2929
        imgs = post.find_all('img')
2930
        return {
2931
            'title': title,
2932
            'description': desc,
2933
            'url2': short_url,
2934
            'img': [i['src'] for i in imgs],
2935
            'month': day.month,
2936
            'year': day.year,
2937
            'day': day.day,
2938
        }
2939
2940
2941
class Optipess(GenericNavigableComic):
2942
    """Class to retrieve Optipess comics."""
2943
    name = 'optipess'
2944
    long_name = 'Optipess'
2945
    url = 'http://www.optipess.com'
2946
    get_first_comic_link = get_a_navi_navifirst
2947
    get_navi_link = get_link_rel_next
2948
2949
    @classmethod
2950
    def get_comic_info(cls, soup, link):
2951
        """Get information about a particular comics."""
2952
        title = soup.find('h2', class_='post-title').string
2953
        author = soup.find("span", class_="post-author").find("a").string
2954
        comic = soup.find('div', id='comic')
2955
        imgs = comic.find_all('img') if comic else []
2956
        alt = imgs[0]['title'] if imgs else ""
2957
        assert all(i['alt'] == i['title'] == alt for i in imgs)
2958
        date_str = soup.find('span', class_='post-date').string
2959
        day = string_to_date(date_str, "%B %d, %Y")
2960
        return {
2961
            'title': title,
2962
            'alt': alt,
2963
            'author': author,
2964
            'img': [i['src'] for i in imgs],
2965
            'month': day.month,
2966
            'year': day.year,
2967
            'day': day.day,
2968
        }
2969
2970
2971
class PainTrainComic(GenericNavigableComic):
2972
    """Class to retrieve Pain Train Comics."""
2973
    name = 'paintrain'
2974
    long_name = 'Pain Train Comics'
2975
    url = 'http://paintraincomic.com'
2976
    get_first_comic_link = get_a_navi_navifirst
2977
    get_navi_link = get_link_rel_next
2978
2979
    @classmethod
2980
    def get_comic_info(cls, soup, link):
2981
        """Get information about a particular comics."""
2982
        title = soup.find('h2', class_='post-title').string
2983
        short_url = soup.find('link', rel='shortlink')['href']
2984
        short_url_re = re.compile('^%s/\\?p=([0-9]*)' % cls.url)
2985
        num = int(short_url_re.match(short_url).groups()[0])
2986
        imgs = soup.find('div', id='comic').find_all('img')
2987
        alt = imgs[0]['title']
2988
        assert all(i['alt'] == i['title'] == alt for i in imgs)
2989
        date_str = soup.find('span', class_='post-date').string
2990
        day = string_to_date(date_str, "%d/%m/%Y")
2991
        return {
2992
            'short_url': short_url,
2993
            'num': num,
2994
            'img': [i['src'] for i in imgs],
2995
            'month': day.month,
2996
            'year': day.year,
2997
            'day': day.day,
2998
            'alt': alt,
2999 View Code Duplication
            'title': title,
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
3000
        }
3001
3002
3003
class MoonBeard(GenericNavigableComic):
3004
    """Class to retrieve MoonBeard comics."""
3005
    # Also on http://squireseses.tumblr.com
3006
    # Also on http://www.webtoons.com/en/comedy/moon-beard/list?title_no=471
3007
    name = 'moonbeard'
3008
    long_name = 'Moon Beard'
3009
    url = 'http://moonbeard.com'
3010
    _categories = ('MOONBEARD', )
3011
    get_first_comic_link = get_a_navi_navifirst
3012
    get_navi_link = get_a_navi_navinext
3013
3014
    @classmethod
3015
    def get_comic_info(cls, soup, link):
3016
        """Get information about a particular comics."""
3017
        title = soup.find('h2', class_='post-title').string
3018
        short_url = soup.find('link', rel='shortlink')['href']
3019
        short_url_re = re.compile('^%s/\\?p=([0-9]*)' % cls.url)
3020
        num = int(short_url_re.match(short_url).groups()[0])
3021
        imgs = soup.find('div', id='comic').find_all('img')
3022
        alt = imgs[0]['title']
3023
        assert all(i['alt'] == i['title'] == alt for i in imgs)
3024
        date_str = soup.find('span', class_='post-date').string
3025
        day = string_to_date(date_str, "%B %d, %Y")
3026
        tags = ' '.join(t['content'] for t in soup.find_all('meta', property='article:tag'))
3027
        author = soup.find('span', class_='post-author').string
3028
        return {
3029
            'short_url': short_url,
3030
            'num': num,
3031
            'img': [i['src'] for i in imgs],
3032
            'month': day.month,
3033
            'year': day.year,
3034
            'day': day.day,
3035
            'title': title,
3036
            'tags': tags,
3037
            'alt': alt,
3038
            'author': author,
3039
        }
3040
3041
3042
class SystemComic(GenericNavigableComic):
3043
    """Class to retrieve System Comic."""
3044
    name = 'system'
3045
    long_name = 'System Comic'
3046
    url = 'http://www.systemcomic.com'
3047
    get_navi_link = get_a_rel_next
3048
3049
    @classmethod
3050
    def get_first_comic_link(cls):
3051
        """Get link to first comics."""
3052
        return get_soup_at_url(cls.url).find('li', class_='first').find('a')
3053
3054
    @classmethod
3055
    def get_comic_info(cls, soup, link):
3056 View Code Duplication
        """Get information about a particular comics."""
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
3057
        title = soup.find('meta', property='og:title')['content']
3058
        desc = soup.find('meta', property='og:description')['content']
3059
        date_str = soup.find('time')["datetime"]
3060
        day = string_to_date(date_str, "%Y-%m-%d")
3061
        imgs = soup.find('figure').find_all('img')
3062
        return {
3063
            'title': title,
3064
            'description': desc,
3065
            'day': day.day,
3066
            'month': day.month,
3067
            'year': day.year,
3068
            'img': [i['src'] for i in imgs],
3069
        }
3070
3071
3072
class LittleLifeLines(GenericNavigableComic):
3073
    """Class to retrieve Little Life Lines comics."""
3074
    # Also on https://little-life-lines.tumblr.com
3075
    name = 'life'
3076
    long_name = 'Little Life Lines'
3077
    url = 'http://www.littlelifelines.com'
3078
    get_url_from_link = join_cls_url_to_href
3079
    get_first_comic_link = simulate_first_link
3080
    first_url = 'http://www.littlelifelines.com/comics/well-done'
3081
3082
    @classmethod
3083
    def get_navi_link(cls, last_soup, next_):
3084
        """Get link to next or previous comic."""
3085
        # prev is next / next is prev
3086
        li = last_soup.find('li', class_='prev' if next_ else 'next')
3087
        return li.find('a') if li else None
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
        desc = soup.find('meta', property='og:description')['content']
3094
        date_str = soup.find('time', class_='published')['datetime']
3095
        day = string_to_date(date_str, "%Y-%m-%d")
3096
        author = soup.find('a', rel='author').string
3097
        div_content = soup.find('div', class_="body entry-content")
3098
        imgs = div_content.find_all('img')
3099
        imgs = [i for i in imgs if i.get('src') is not None]
3100
        alt = imgs[0]['alt']
3101
        return {
3102
            'title': title,
3103
            'alt': alt,
3104
            'description': desc,
3105
            'author': author,
3106
            'day': day.day,
3107
            'month': day.month,
3108
            'year': day.year,
3109
            'img': [i['src'] for i in imgs],
3110
        }
3111
3112
3113
class GenericWordPressInkblot(GenericNavigableComic):
3114
    """Generic class to retrieve comics using WordPress with Inkblot."""
3115
    get_navi_link = get_link_rel_next
3116
3117
    @classmethod
3118
    def get_first_comic_link(cls):
3119
        """Get link to first comics."""
3120
        return get_soup_at_url(cls.url).find('a', class_='webcomic-link webcomic1-link first-webcomic-link first-webcomic1-link')
3121
3122
    @classmethod
3123
    def get_comic_info(cls, soup, link):
3124
        """Get information about a particular comics."""
3125
        title = soup.find('meta', property='og:title')['content']
3126
        imgs = soup.find('div', class_='webcomic-image').find_all('img')
3127
        date_str = soup.find('meta', property='article:published_time')['content'][:10]
3128
        day = string_to_date(date_str, "%Y-%m-%d")
3129
        return {
3130
            'title': title,
3131
            'day': day.day,
3132
            'month': day.month,
3133
            'year': day.year,
3134
            'img': [i['src'] for i in imgs],
3135
        }
3136
3137
3138
class EverythingsStupid(GenericWordPressInkblot):
3139
    """Class to retrieve Everything's stupid Comics."""
3140
    # Also on http://tapastic.com/series/EverythingsStupid
3141
    # Also on http://www.webtoons.com/en/challenge/everythings-stupid/list?title_no=14591
3142
    # Also on http://everythingsstupidcomics.tumblr.com
3143
    name = 'stupid'
3144
    long_name = "Everything's Stupid"
3145
    url = 'http://everythingsstupid.net'
3146
3147
3148
class TheIsmComics(GenericDeletedComic, GenericWordPressInkblot):
3149
    """Class to retrieve The Ism Comics."""
3150
    # Also on https://tapastic.com/series/TheIsm (?)
3151
    name = 'theism'
3152
    long_name = "The Ism"
3153
    url = 'http://www.theism-comics.com'
3154
3155
3156
class WoodenPlankStudios(GenericWordPressInkblot):
3157
    """Class to retrieve Wooden Plank Studios comics."""
3158
    name = 'woodenplank'
3159
    long_name = 'Wooden Plank Studios'
3160
    url = 'http://woodenplankstudios.com'
3161
3162
3163
class ElectricBunnyComic(GenericNavigableComic):
3164
    """Class to retrieve Electric Bunny Comics."""
3165
    # Also on http://electricbunnycomics.tumblr.com
3166
    name = 'bunny'
3167
    long_name = 'Electric Bunny Comic'
3168
    url = 'http://www.electricbunnycomics.com/View/Comic/153/Welcome+to+Hell'
3169
    get_url_from_link = join_cls_url_to_href
3170
3171
    @classmethod
3172
    def get_first_comic_link(cls):
3173
        """Get link to first comics."""
3174
        return get_soup_at_url(cls.url).find('img', alt='First').parent
3175
3176
    @classmethod
3177
    def get_navi_link(cls, last_soup, next_):
3178
        """Get link to next or previous comic."""
3179
        img = last_soup.find('img', alt='Next' if next_ else 'Back')
3180
        return img.parent if img else None
3181
3182
    @classmethod
3183
    def get_comic_info(cls, soup, link):
3184
        """Get information about a particular comics."""
3185
        title = soup.find('meta', property='og:title')['content']
3186
        imgs = soup.find_all('meta', property='og:image')
3187
        return {
3188
            'title': title,
3189
            'img': [i['content'] for i in imgs],
3190
        }
3191
3192
3193
class SheldonComics(GenericNavigableComic):
3194
    """Class to retrieve Sheldon comics."""
3195
    # Also on http://www.gocomics.com/sheldon
3196
    name = 'sheldon'
3197
    long_name = 'Sheldon Comics'
3198
    url = 'http://www.sheldoncomics.com'
3199
3200
    @classmethod
3201
    def get_first_comic_link(cls):
3202
        """Get link to first comics."""
3203
        return get_soup_at_url(cls.url).find("a", id="nav-first")
3204
3205
    @classmethod
3206
    def get_navi_link(cls, last_soup, next_):
3207
        """Get link to next or previous comic."""
3208
        for link in last_soup.find_all("a", id="nav-next" if next_ else "nav-prev"):
3209
            if link['href'] != 'http://www.sheldoncomics.com':
3210
                return link
3211
        return None
3212
3213
    @classmethod
3214
    def get_comic_info(cls, soup, link):
3215
        """Get information about a particular comics."""
3216
        imgs = soup.find("div", id="comic-foot").find_all("img")
3217
        assert all(i['alt'] == i['title'] for i in imgs)
3218
        assert len(imgs) == 1, imgs
3219
        title = imgs[0]['title']
3220
        return {
3221
            'title': title,
3222
            'img': [i['src'] for i in imgs],
3223
        }
3224
3225
3226
class ManVersusManatee(GenericNavigableComic):
3227
    """Class to retrieve Man Versus Manatee comics."""
3228
    url = 'http://manvsmanatee.com'
3229
    name = 'manvsmanatee'
3230
    long_name = 'Man Versus Manatee'
3231
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
3232
    get_navi_link = get_a_comicnavbase_comicnavnext
3233
3234
    @classmethod
3235
    def get_comic_info(cls, soup, link):
3236
        """Get information about a particular comics."""
3237
        title = soup.find('h2', class_='post-title').string
3238
        imgs = soup.find('div', id='comic').find_all('img')
3239
        date_str = soup.find('span', class_='post-date').string
3240
        day = string_to_date(date_str, "%B %d, %Y")
3241
        return {
3242
            'img': [i['src'] for i in imgs],
3243
            'title': title,
3244
            'month': day.month,
3245
            'year': day.year,
3246
            'day': day.day,
3247
        }
3248
3249
3250
class TheMeerkatguy(GenericNavigableComic):
3251
    """Class to retrieve The Meerkatguy comics."""
3252
    long_name = 'The Meerkatguy'
3253
    url = 'http://www.themeerkatguy.com'
3254
    name = 'meerkatguy'
3255
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
3256
    get_navi_link = get_a_comicnavbase_comicnavnext
3257
3258
    @classmethod
3259
    def get_comic_info(cls, soup, link):
3260
        """Get information about a particular comics."""
3261
        title = soup.find('title').string
3262
        imgs = soup.find_all('meta', property='og:image')
3263
        return {
3264
            'img': [i['content'] for i in imgs],
3265
            'title': title,
3266
        }
3267
3268
3269
class Ubertool(GenericNavigableComic):
3270
    """Class to retrieve Ubertool comics."""
3271
    # Also on https://ubertool.tumblr.com
3272
    # Also on https://tapastic.com/series/ubertool
3273
    name = 'ubertool'
3274
    long_name = 'Ubertool'
3275
    url = 'http://ubertoolcomic.com'
3276
    _categories = ('UBERTOOL', )
3277
    get_first_comic_link = get_a_comicnavbase_comicnavfirst
3278
    get_navi_link = get_a_comicnavbase_comicnavnext
3279
3280
    @classmethod
3281
    def get_comic_info(cls, soup, link):
3282
        """Get information about a particular comics."""
3283
        title = soup.find('h2', class_='post-title').string
3284
        date_str = soup.find('span', class_='post-date').string
3285
        day = string_to_date(date_str, "%B %d, %Y")
3286
        imgs = soup.find('div', id='comic').find_all('img')
3287
        return {
3288
            'img': [i['src'] for i in imgs],
3289
            'title': title,
3290
            'month': day.month,
3291
            'year': day.year,
3292
            'day': day.day,
3293
        }
3294
3295
3296
class EarthExplodes(GenericNavigableComic):
3297
    """Class to retrieve The Earth Explodes comics."""
3298
    name = 'earthexplodes'
3299
    long_name = 'The Earth Explodes'
3300
    url = 'http://www.earthexplodes.com'
3301
    get_url_from_link = join_cls_url_to_href
3302
    get_first_comic_link = simulate_first_link
3303
    first_url = 'http://www.earthexplodes.com/comics/000/'
3304
3305
    @classmethod
3306
    def get_navi_link(cls, last_soup, next_):
3307
        """Get link to next or previous comic."""
3308
        return last_soup.find('a', id='next' if next_ else 'prev')
3309
3310
    @classmethod
3311
    def get_comic_info(cls, soup, link):
3312
        """Get information about a particular comics."""
3313
        title = soup.find('title').string
3314
        imgs = soup.find('div', id='image').find_all('img')
3315
        alt = imgs[0].get('title', '')
3316
        return {
3317
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
3318
            'title': title,
3319
            'alt': alt,
3320
        }
3321
3322
3323
class PomComics(GenericNavigableComic):
3324
    """Class to retrieve PomComics."""
3325
    name = 'pom'
3326
    long_name = 'Pom Comics / Piece of Me'
3327
    url = 'http://www.pomcomic.com'
3328
    get_url_from_link = join_cls_url_to_href
3329
3330
    @classmethod
3331
    def get_first_comic_link(cls):
3332
        """Get link to first comics."""
3333
        return get_soup_at_url(cls.url).find('a', class_='btn-first')
3334
3335
    @classmethod
3336
    def get_navi_link(cls, last_soup, next_):
3337
        """Get link to next or previous comic."""
3338
        return last_soup.find('a', class_='btn-next' if next_ else 'btn-prev')
3339
3340
    @classmethod
3341
    def get_comic_info(cls, soup, link):
3342
        """Get information about a particular comics."""
3343
        title = soup.find('h1').string
3344
        desc = soup.find('meta', property='og:description')['content']
3345
        tags = soup.find('meta', attrs={'name': 'keywords'})['content']
3346
        imgs = soup.find('div', class_='comic').find_all('img')
3347
        return {
3348
            'title': title,
3349
            'desc': desc,
3350
            'tags': tags,
3351
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
3352
        }
3353
3354
3355
class CubeDrone(GenericComicNotWorking, GenericNavigableComic):  # Website has changed
3356
    """Class to retrieve Cube Drone comics."""
3357
    name = 'cubedrone'
3358
    long_name = 'Cube Drone'
3359
    url = 'http://cube-drone.com/comics'
3360
    get_url_from_link = join_cls_url_to_href
3361
3362
    @classmethod
3363
    def get_first_comic_link(cls):
3364
        """Get link to first comics."""
3365
        return get_soup_at_url(cls.url).find('span', class_='glyphicon glyphicon-backward').parent
3366
3367
    @classmethod
3368
    def get_navi_link(cls, last_soup, next_):
3369
        """Get link to next or previous comic."""
3370
        class_ = 'glyphicon glyphicon-chevron-' + ('right' if next_ else 'left')
3371
        return last_soup.find('span', class_=class_).parent
3372
3373
    @classmethod
3374
    def get_comic_info(cls, soup, link):
3375
        """Get information about a particular comics."""
3376
        title = soup.find('meta', attrs={'name': 'twitter:title'})['content']
3377
        url2 = soup.find('meta', attrs={'name': 'twitter:url'})['content']
3378
        # date_str = soup.find('h2', class_='comic_title').find('small').string
3379
        # day = string_to_date(date_str, "%B %d, %Y, %I:%M %p")
3380
        imgs = soup.find_all('img', class_='comic img-responsive')
3381
        title2 = imgs[0]['title']
3382
        alt = imgs[0]['alt']
3383
        return {
3384
            'url2': url2,
3385
            'title': title,
3386
            'title2': title2,
3387
            'alt': alt,
3388
            'img': [i['src'] for i in imgs],
3389
        }
3390
3391
3392
class MakeItStoopid(GenericDeletedComic, GenericNavigableComic):
3393
    """Class to retrieve Make It Stoopid Comics."""
3394
    name = 'stoopid'
3395
    long_name = 'Make it stoopid'
3396
    url = 'http://makeitstoopid.com/comic.php'
3397
3398
    @classmethod
3399
    def get_nav(cls, soup):
3400
        """Get the navigation elements from soup object."""
3401
        cnav = soup.find_all(class_='cnav')
3402
        nav1, nav2 = cnav[:5], cnav[5:]
3403
        assert nav1 == nav2
3404
        # begin, prev, archive, next_, end = nav1
3405
        return [None if i.get('href') is None else i for i in nav1]
3406
3407
    @classmethod
3408
    def get_first_comic_link(cls):
3409
        """Get link to first comics."""
3410
        return cls.get_nav(get_soup_at_url(cls.url))[0]
3411
3412
    @classmethod
3413
    def get_navi_link(cls, last_soup, next_):
3414
        """Get link to next or previous comic."""
3415
        return cls.get_nav(last_soup)[3 if next_ else 1]
3416
3417
    @classmethod
3418
    def get_comic_info(cls, soup, link):
3419
        """Get information about a particular comics."""
3420
        title = link['title']
3421
        imgs = soup.find_all('img', id='comicimg')
3422
        return {
3423
            'title': title,
3424
            'img': [i['src'] for i in imgs],
3425
        }
3426
3427
3428
class OffTheLeashDog(GenericNavigableComic):
3429
    """Class to retrieve Off The Leash Dog comics."""
3430
    # Also on http://rupertfawcettsdoggyblog.tumblr.com
3431
    # Also on http://www.rupertfawcettcartoons.com
3432
    name = 'offtheleash'
3433
    long_name = 'Off The Leash Dog'
3434
    url = 'http://offtheleashdogcartoons.com'
3435
    _categories = ('FAWCETT', )
3436
    get_navi_link = get_a_rel_next
3437
    get_first_comic_link = simulate_first_link
3438
    first_url = 'http://offtheleashdogcartoons.com/uncategorized/can-i-help-you/'
3439
3440
    @classmethod
3441
    def get_comic_info(cls, soup, link):
3442
        """Get information about a particular comics."""
3443
        title = soup.find("h1", class_="entry-title").string
3444
        imgs = soup.find('div', class_='entry-content').find_all('img')
3445
        return {
3446
            'title': title,
3447
            'img': [i['src'] for i in imgs],
3448
        }
3449
3450
3451
class MarketoonistComics(GenericNavigableComic):
3452
    """Class to retrieve Marketoonist Comics."""
3453
    name = 'marketoonist'
3454
    long_name = 'Marketoonist'
3455
    url = 'https://marketoonist.com/cartoons'
3456
    get_first_comic_link = simulate_first_link
3457
    get_navi_link = get_link_rel_next
3458
    first_url = 'https://marketoonist.com/2002/10/the-8-types-of-brand-managers-2.html'
3459
3460
    @classmethod
3461
    def get_comic_info(cls, soup, link):
3462
        """Get information about a particular comics."""
3463
        imgs = soup.find_all('meta', property='og:image')
3464
        date_str = soup.find('meta', property='article:published_time')['content'][:10]
3465
        day = string_to_date(date_str, "%Y-%m-%d")
3466
        title = soup.find('meta', property='og:title')['content']
3467
        return {
3468
            'img': [i['content'] for i in imgs],
3469
            'day': day.day,
3470
            'month': day.month,
3471
            'year': day.year,
3472
            'title': title,
3473
        }
3474
3475
3476
class ConsoliaComics(GenericNavigableComic):
3477
    """Class to retrieve Consolia comics."""
3478
    name = 'consolia'
3479
    long_name = 'consolia'
3480
    url = 'https://consolia-comic.com'
3481
    get_url_from_link = join_cls_url_to_href
3482
3483
    @classmethod
3484
    def get_first_comic_link(cls):
3485
        """Get link to first comics."""
3486
        return get_soup_at_url(cls.url).find('a', class_='first')
3487
3488
    @classmethod
3489
    def get_navi_link(cls, last_soup, next_):
3490
        """Get link to next or previous comic."""
3491
        return last_soup.find('a', class_='next' if next_ else 'prev')
3492
3493
    @classmethod
3494
    def get_comic_info(cls, soup, link):
3495
        """Get information about a particular comics."""
3496
        title = soup.find('meta', property='og:title')['content']
3497
        date_str = soup.find('time')["datetime"]
3498
        day = string_to_date(date_str, "%Y-%m-%d")
3499
        imgs = soup.find_all('meta', property='og:image')
3500
        return {
3501
            'title': title,
3502
            'img': [i['content'] for i in imgs],
3503
            'day': day.day,
3504
            'month': day.month,
3505
            'year': day.year,
3506
        }
3507
3508
3509
class GenericBlogspotComic(GenericNavigableComic):
3510
    """Generic class to retrieve comics from Blogspot."""
3511
    get_first_comic_link = simulate_first_link
3512
    first_url = NotImplemented
3513
    _categories = ('BLOGSPOT', )
3514
3515
    @classmethod
3516
    def get_navi_link(cls, last_soup, next_):
3517
        """Get link to next or previous comic."""
3518
        return last_soup.find('a', id='Blog1_blog-pager-newer-link' if next_ else 'Blog1_blog-pager-older-link')
3519
3520
3521
class TuMourrasMoinsBete(GenericBlogspotComic):
3522
    """Class to retrieve Tu Mourras Moins Bete comics."""
3523
    name = 'mourrasmoinsbete'
3524
    long_name = 'Tu Mourras Moins Bete'
3525
    url = 'http://tumourrasmoinsbete.blogspot.fr'
3526
    _categories = ('FRANCAIS', )
3527
    first_url = 'http://tumourrasmoinsbete.blogspot.fr/2008/06/essai.html'
3528
3529
    @classmethod
3530
    def get_comic_info(cls, soup, link):
3531
        """Get information about a particular comics."""
3532
        title = soup.find('title').string
3533
        imgs = soup.find('div', itemprop='description articleBody').find_all('img')
3534
        author = soup.find('span', itemprop='author').string
3535
        return {
3536
            'img': [i['src'] for i in imgs],
3537
            'author': author,
3538
            'title': title,
3539
        }
3540
3541
3542
class Octopuns(GenericBlogspotComic):
3543
    """Class to retrieve Octopuns comics."""
3544
    # Also on http://octopuns.tumblr.com
3545
    name = 'octopuns'
3546
    long_name = 'Octopuns'
3547
    url = 'http://www.octopuns.net'  # or http://octopuns.blogspot.fr/
3548
    first_url = 'http://octopuns.blogspot.com/2010/12/17122010-always-read-label.html'
3549
3550
    @classmethod
3551
    def get_comic_info(cls, soup, link):
3552
        """Get information about a particular comics."""
3553
        title = soup.find('h3', class_='post-title entry-title').string
3554
        date_str = soup.find('h2', class_='date-header').string
3555
        day = string_to_date(date_str, "%A, %B %d, %Y")
3556
        imgs = soup.find_all('link', rel='image_src')
3557
        return {
3558
            'img': [i['href'] for i in imgs],
3559
            'title': title,
3560
            'day': day.day,
3561
            'month': day.month,
3562
            'year': day.year,
3563
        }
3564
3565
3566
class GeekAndPoke(GenericNavigableComic):
3567
    """Class to retrieve Geek And Poke comics."""
3568
    name = 'geek'
3569
    long_name = 'Geek And Poke'
3570
    url = 'http://geek-and-poke.com'
3571
    get_url_from_link = join_cls_url_to_href
3572
    get_first_comic_link = simulate_first_link
3573
    first_url = 'http://geek-and-poke.com/geekandpoke/2006/8/27/a-new-place-for-a-not-so-old-blog.html'
3574
3575
    @classmethod
3576
    def get_navi_link(cls, last_soup, next_):
3577
        """Get link to next or previous comic."""
3578
        return last_soup.find('a', class_='prev-item' if next_ else 'next-item')
3579
3580
    @classmethod
3581
    def get_comic_info(cls, soup, link):
3582
        """Get information about a particular comics."""
3583
        title = soup.find('meta', property='og:title')['content']
3584
        desc = soup.find('meta', property='og:description')
3585
        desc_str = "" if desc is None else desc['content']
3586
        date_str = soup.find('time', class_='published')['datetime']
3587
        day = string_to_date(date_str, "%Y-%m-%d")
3588
        author = soup.find('a', rel='author').string
3589
        div_content = (soup.find('div', class_="body entry-content") or
3590
                       soup.find('div', class_="special-content"))
3591
        imgs = div_content.find_all('img')
3592
        imgs = [i for i in imgs if i.get('src') is not None]
3593
        assert all('title' not in i or i['alt'] == i['title'] for i in imgs)
3594
        alt = imgs[0].get('alt', "") if imgs else []
3595
        return {
3596
            'title': title,
3597
            'alt': alt,
3598
            'description': desc_str,
3599
            'author': author,
3600
            'day': day.day,
3601
            'month': day.month,
3602
            'year': day.year,
3603
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
3604
        }
3605
3606
3607
class GloryOwlComix(GenericBlogspotComic):
3608
    """Class to retrieve Glory Owl comics."""
3609
    name = 'gloryowl'
3610
    long_name = 'Glory Owl'
3611
    url = 'http://gloryowlcomix.blogspot.fr'
3612
    _categories = ('NSFW', 'FRANCAIS')
3613
    first_url = 'http://gloryowlcomix.blogspot.fr/2013/02/1_7.html'
3614
3615
    @classmethod
3616
    def get_comic_info(cls, soup, link):
3617
        """Get information about a particular comics."""
3618
        title = soup.find('title').string
3619
        imgs = soup.find_all('link', rel='image_src')
3620
        author = soup.find('a', rel='author').string
3621
        return {
3622
            'img': [i['href'] for i in imgs],
3623
            'author': author,
3624
            'title': title,
3625
        }
3626
3627
3628 View Code Duplication
class AtRandomComics(GenericNavigableComic):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
3629
    """Class to retrieve At Random Comics."""
3630
    name = 'atrandom'
3631
    long_name = 'At Random Comics'
3632
    url = 'http://www.atrandomcomics.com'
3633
    get_url_from_link = join_cls_url_to_href
3634
    get_first_comic_link = simulate_first_link
3635
    first_url = 'http://www.atrandomcomics.com/at-random-comics-home/2015/5/5/can-of-worms'
3636
3637
    @classmethod
3638
    def get_navi_link(cls, last_soup, next_):
3639
        """Get link to next or previous comic."""
3640
        return last_soup.find('a', id='prevLink' if next_ else 'nextLink')
3641
3642
    @classmethod
3643
    def get_comic_info(cls, soup, link):
3644
        """Get information about a particular comics."""
3645
        title = soup.find('meta', property='og:title')['content']
3646
        desc = soup.find('meta', property='og:description')['content']
3647
        date_str = soup.find('time', itemprop='datePublished')["datetime"]
3648
        day = string_to_date(date_str, "%Y-%m-%d")
3649
        author = soup.find('a', rel='author').string
3650
        imgs = soup.find_all('meta', property='og:image')
3651
        return {
3652
            'title': title,
3653
            'img': [i['content'] for i in imgs],
3654
            'month': day.month,
3655
            'year': day.year,
3656
            'day': day.day,
3657
            'author': author,
3658
            'description': desc,
3659
        }
3660
3661
3662
class GenericTumblrV1(GenericComic):
3663
    """Generic class to retrieve comics from Tumblr using the V1 API."""
3664
    _categories = ('TUMBLR', )
3665
3666
    @classmethod
3667
    def get_next_comic(cls, last_comic):
3668
        """Generic implementation of get_next_comic for Tumblr comics."""
3669
        for p in cls.get_posts(last_comic):
3670
            comic = cls.get_comic_info(p)
3671
            if comic is not None:
3672
                yield comic
3673
3674
    @classmethod
3675
    def check_url(cls, url):
3676
        if not url.startswith(cls.url):
3677
            print("url '%s' does not start with '%s'" % (url, cls.url))
3678
        return url
3679
3680
    @classmethod
3681
    def get_url_from_post(cls, post):
3682
        return cls.check_url(post['url'])
3683
3684
    @classmethod
3685
    def get_api_url(cls):
3686
        return urljoin_wrapper(cls.url, '/api/read/')
3687
3688
    @classmethod
3689
    def get_api_url_for_id(cls, tumblr_id):
3690
        return cls.get_api_url() + '?id=%d' % (tumblr_id)
3691
3692
    @classmethod
3693
    def get_comic_info(cls, post):
3694
        """Get information about a particular comics."""
3695
        type_ = post['type']
3696
        if type_ != 'photo':
3697
            return None
3698
        tumblr_id = int(post['id'])
3699
        api_url = cls.get_api_url_for_id(tumblr_id)
3700
        day = datetime.datetime.fromtimestamp(int(post['unix-timestamp'])).date()
3701
        caption = post.find('photo-caption')
3702
        title = caption.string if caption else ""
3703
        tags = ' '.join(t.string for t in post.find_all('tag'))
3704
        # Photos may appear in 'photo' tags and/or straight in the post
3705
        photo_tags = post.find_all('photo')
3706
        if not photo_tags:
3707
            photo_tags = [post]
3708
        # Images are in multiple resolutions - taking the first one
3709
        imgs = [photo.find('photo-url') for photo in photo_tags]
3710
        return {
3711
            'url': cls.get_url_from_post(post),
3712
            'url2': post['url-with-slug'],
3713
            'day': day.day,
3714
            'month': day.month,
3715
            'year': day.year,
3716
            'title': title,
3717
            'tags': tags,
3718
            'img': [i.string for i in imgs],
3719
            'tumblr-id': tumblr_id,
3720
            'api_url': api_url,
3721
        }
3722
3723
    @classmethod
3724
    def get_posts(cls, last_comic, nb_post_per_call=10):
3725
        """Get posts using API. nb_post_per_call is max 50.
3726
3727
        Posts are retrieved from newer to older as per the tumblr v1 api
3728
        but are returned in chronological order."""
3729
        waiting_for_id = last_comic['tumblr-id'] if last_comic else None
3730
        posts_acc = []
3731
        if last_comic is not None:
3732
            # cls.check_url(last_comic['url'])
3733
            cls.check_url(last_comic['api_url'])
3734
            # Sometimes, tumblr posts are deleted. When previous post is deleted, we
3735
            # might end up spending a lot of time looking for something that
3736
            # doesn't exist. Failing early and clearly might be a better option.
3737
            last_api_url = cls.get_api_url_for_id(waiting_for_id)
3738
            try:
3739
                get_soup_at_url(last_api_url)
3740
            except urllib.error.HTTPError:
3741
                try:
3742
                    get_soup_at_url(cls.url)
3743
                except urllib.error.HTTPError:
3744
                    print("Did not find previous post nor main url %s" % cls.url)
3745
                else:
3746
                    print("Did not find previous post %s : it might have been deleted" % last_api_url)
3747
                return reversed(posts_acc)
3748
        api_url = cls.get_api_url()
3749
        posts = get_soup_at_url(api_url).find('posts')
3750
        start, total = int(posts['start']), int(posts['total'])
3751
        assert start == 0
3752
        for starting_num in range(0, total, nb_post_per_call):
3753
            api_url2 = api_url + '?start=%d&num=%d' % (starting_num, nb_post_per_call)
3754
            posts2 = get_soup_at_url(api_url2).find('posts')
3755
            start2, total2 = int(posts2['start']), int(posts2['total'])
3756
            assert starting_num == start2, "%d != %d" % (starting_num, start2)
3757
            # This may happen and should be handled in the future
3758
            assert total == total2, "%d != %d" % (total, total2)
3759
            for p in posts2.find_all('post'):
3760
                tumblr_id = int(p['id'])
3761
                if waiting_for_id and waiting_for_id == tumblr_id:
3762
                    return reversed(posts_acc)
3763
                posts_acc.append(p)
3764
        if waiting_for_id is None:
3765
            return reversed(posts_acc)
3766
        print("Did not find %s : there might be a problem" % waiting_for_id)
3767
        return []
3768
3769
3770
class SaturdayMorningBreakfastCerealTumblr(GenericTumblrV1):
3771
    """Class to retrieve Saturday Morning Breakfast Cereal comics."""
3772
    # Also on http://www.gocomics.com/saturday-morning-breakfast-cereal
3773
    # Also on http://www.smbc-comics.com
3774
    name = 'smbc-tumblr'
3775
    long_name = 'Saturday Morning Breakfast Cereal (from Tumblr)'
3776
    url = 'http://smbc-comics.tumblr.com'
3777
    _categories = ('SMBC', )
3778
3779
3780
class AHammADay(GenericTumblrV1):
3781
    """Class to retrieve class A Hamm A Day comics."""
3782
    name = 'hamm'
3783
    long_name = 'A Hamm A Day'
3784
    url = 'http://www.ahammaday.com'
3785
3786
3787
class IrwinCardozo(GenericTumblrV1):
3788
    """Class to retrieve Irwin Cardozo Comics."""
3789
    name = 'irwinc'
3790
    long_name = 'Irwin Cardozo'
3791
    url = 'http://irwincardozocomics.tumblr.com'
3792
3793
3794
class AccordingToDevin(GenericTumblrV1):
3795
    """Class to retrieve According To Devin comics."""
3796
    name = 'devin'
3797
    long_name = 'According To Devin'
3798
    url = 'http://accordingtodevin.tumblr.com'
3799
3800
3801
class ItsTheTieTumblr(GenericTumblrV1):
3802
    """Class to retrieve It's the tie comics."""
3803
    # Also on http://itsthetie.com
3804
    # Also on https://tapastic.com/series/itsthetie
3805
    name = 'tie-tumblr'
3806
    long_name = "It's the tie (from Tumblr)"
3807
    url = "http://itsthetie.tumblr.com"
3808
    _categories = ('TIE', )
3809
3810
3811
class OctopunsTumblr(GenericTumblrV1):
3812
    """Class to retrieve Octopuns comics."""
3813
    # Also on http://www.octopuns.net
3814
    name = 'octopuns-tumblr'
3815
    long_name = 'Octopuns (from Tumblr)'
3816
    url = 'http://octopuns.tumblr.com'
3817
3818
3819
class PicturesInBoxesTumblr(GenericTumblrV1):
3820
    """Class to retrieve Pictures In Boxes comics."""
3821
    # Also on http://www.picturesinboxes.com
3822
    name = 'picturesinboxes-tumblr'
3823
    long_name = 'Pictures in Boxes (from Tumblr)'
3824
    url = 'https://picturesinboxescomic.tumblr.com'
3825
3826
3827
class TubeyToonsTumblr(GenericTumblrV1):
3828
    """Class to retrieve TubeyToons comics."""
3829
    # Also on http://tapastic.com/series/Tubey-Toons
3830
    # Also on http://tubeytoons.com
3831
    name = 'tubeytoons-tumblr'
3832
    long_name = 'Tubey Toons (from Tumblr)'
3833
    url = 'https://tubeytoons.tumblr.com'
3834
    _categories = ('TUNEYTOONS', )
3835
3836
3837
class UnearthedComicsTumblr(GenericTumblrV1):
3838
    """Class to retrieve Unearthed comics."""
3839
    # Also on http://tapastic.com/series/UnearthedComics
3840
    # Also on http://unearthedcomics.com
3841
    name = 'unearthed-tumblr'
3842
    long_name = 'Unearthed Comics (from Tumblr)'
3843
    url = 'https://unearthedcomics.tumblr.com'
3844
    _categories = ('UNEARTHED', )
3845
3846
3847
class PieComic(GenericTumblrV1):
3848
    """Class to retrieve Pie Comic comics."""
3849
    name = 'pie'
3850
    long_name = 'Pie Comic'
3851
    url = "http://piecomic.tumblr.com"
3852
3853
3854
class MrEthanDiamond(GenericTumblrV1):
3855
    """Class to retrieve Mr Ethan Diamond comics."""
3856
    name = 'diamond'
3857
    long_name = 'Mr Ethan Diamond'
3858
    url = 'http://mrethandiamond.tumblr.com'
3859
3860
3861
class Flocci(GenericTumblrV1):
3862
    """Class to retrieve floccinaucinihilipilification comics."""
3863
    name = 'flocci'
3864
    long_name = 'floccinaucinihilipilification'
3865
    url = "http://floccinaucinihilipilificationa.tumblr.com"
3866
3867
3868
class UpAndOut(GenericTumblrV1):
3869
    """Class to retrieve Up & Out comics."""
3870
    # Also on http://tapastic.com/series/UP-and-OUT
3871
    name = 'upandout'
3872
    long_name = 'Up And Out (from Tumblr)'
3873
    url = 'http://upandoutcomic.tumblr.com'
3874
3875
3876
class Pundemonium(GenericTumblrV1):
3877
    """Class to retrieve Pundemonium comics."""
3878
    name = 'pundemonium'
3879
    long_name = 'Pundemonium'
3880
    url = 'http://monstika.tumblr.com'
3881
3882
3883
class PoorlyDrawnLinesTumblr(GenericTumblrV1):
3884
    """Class to retrieve Poorly Drawn Lines comics."""
3885
    # Also on http://poorlydrawnlines.com
3886
    name = 'poorlydrawn-tumblr'
3887
    long_name = 'Poorly Drawn Lines (from Tumblr)'
3888
    url = 'http://pdlcomics.tumblr.com'
3889
    _categories = ('POORLYDRAWN', )
3890
3891
3892
class PearShapedComics(GenericTumblrV1):
3893
    """Class to retrieve Pear Shaped Comics."""
3894
    name = 'pearshaped'
3895
    long_name = 'Pear-Shaped Comics'
3896
    url = 'http://pearshapedcomics.com'
3897
3898
3899
class PondScumComics(GenericTumblrV1):
3900
    """Class to retrieve Pond Scum Comics."""
3901
    name = 'pond'
3902
    long_name = 'Pond Scum'
3903
    url = 'http://pondscumcomic.tumblr.com'
3904
3905
3906
class MercworksTumblr(GenericTumblrV1):
3907
    """Class to retrieve Mercworks comics."""
3908
    # Also on http://mercworks.net
3909
    name = 'mercworks-tumblr'
3910
    long_name = 'Mercworks (from Tumblr)'
3911
    url = 'http://mercworks.tumblr.com'
3912
3913
3914
class OwlTurdTumblr(GenericTumblrV1):
3915
    """Class to retrieve Owl Turd comics."""
3916
    # Also on http://tapastic.com/series/Owl-Turd-Comix
3917
    name = 'owlturd-tumblr'
3918
    long_name = 'Owl Turd (from Tumblr)'
3919
    url = 'http://owlturd.com'
3920
    _categories = ('OWLTURD', )
3921
3922
3923
class VectorBelly(GenericTumblrV1):
3924
    """Class to retrieve Vector Belly comics."""
3925
    # Also on http://vectorbelly.com
3926
    name = 'vector'
3927
    long_name = 'Vector Belly'
3928
    url = 'http://vectorbelly.tumblr.com'
3929
3930
3931
class GoneIntoRapture(GenericTumblrV1):
3932
    """Class to retrieve Gone Into Rapture comics."""
3933
    # Also on http://goneintorapture.tumblr.com
3934
    # Also on http://tapastic.com/series/Goneintorapture
3935
    name = 'rapture'
3936
    long_name = 'Gone Into Rapture'
3937
    url = 'http://goneintorapture.com'
3938
3939
3940
class TheOatmealTumblr(GenericTumblrV1):
3941
    """Class to retrieve The Oatmeal comics."""
3942
    # Also on http://theoatmeal.com
3943
    name = 'oatmeal-tumblr'
3944
    long_name = 'The Oatmeal (from Tumblr)'
3945
    url = 'http://oatmeal.tumblr.com'
3946
3947
3948
class HeckIfIKnowComicsTumblr(GenericTumblrV1):
3949
    """Class to retrieve Heck If I Know Comics."""
3950
    # Also on http://tapastic.com/series/Regular
3951
    name = 'heck-tumblr'
3952
    long_name = 'Heck if I Know comics (from Tumblr)'
3953
    url = 'http://heckifiknowcomics.com'
3954
3955
3956
class MyJetPack(GenericTumblrV1):
3957
    """Class to retrieve My Jet Pack comics."""
3958
    name = 'jetpack'
3959
    long_name = 'My Jet Pack'
3960
    url = 'http://myjetpack.tumblr.com'
3961
3962
3963
class CheerUpEmoKidTumblr(GenericTumblrV1):
3964
    """Class to retrieve CheerUpEmoKid comics."""
3965
    # Also on http://www.cheerupemokid.com
3966
    # Also on http://tapastic.com/series/CUEK
3967
    name = 'cuek-tumblr'
3968
    long_name = 'Cheer Up Emo Kid (from Tumblr)'
3969
    url = 'https://enzocomics.tumblr.com'
3970
3971
3972
class ForLackOfABetterComic(GenericTumblrV1):
3973
    """Class to retrieve For Lack Of A Better Comics."""
3974
    # Also on http://forlackofabettercomic.com
3975
    name = 'lack'
3976
    long_name = 'For Lack Of A Better Comic'
3977
    url = 'http://forlackofabettercomic.tumblr.com'
3978
3979
3980
class ZenPencilsTumblr(GenericTumblrV1):
3981
    """Class to retrieve ZenPencils comics."""
3982
    # Also on http://zenpencils.com
3983
    # Also on http://www.gocomics.com/zen-pencils
3984
    name = 'zenpencils-tumblr'
3985
    long_name = 'Zen Pencils (from Tumblr)'
3986
    url = 'http://zenpencils.tumblr.com'
3987
    _categories = ('ZENPENCILS', )
3988
3989
3990
class ThreeWordPhraseTumblr(GenericTumblrV1):
3991
    """Class to retrieve Three Word Phrase comics."""
3992
    # Also on http://threewordphrase.com
3993
    name = 'threeword-tumblr'
3994
    long_name = 'Three Word Phrase (from Tumblr)'
3995
    url = 'http://threewordphrase.tumblr.com'
3996
3997
3998
class TimeTrabbleTumblr(GenericTumblrV1):
3999
    """Class to retrieve Time Trabble comics."""
4000
    # Also on http://timetrabble.com
4001
    name = 'timetrabble-tumblr'
4002
    long_name = 'Time Trabble (from Tumblr)'
4003
    url = 'http://timetrabble.tumblr.com'
4004
4005
4006
class SafelyEndangeredTumblr(GenericTumblrV1):
4007
    """Class to retrieve Safely Endangered comics."""
4008
    # Also on http://www.safelyendangered.com
4009
    name = 'endangered-tumblr'
4010
    long_name = 'Safely Endangered (from Tumblr)'
4011
    url = 'http://tumblr.safelyendangered.com'
4012
4013
4014
class MouseBearComedyTumblr(GenericTumblrV1):
4015
    """Class to retrieve Mouse Bear Comedy comics."""
4016
    # Also on http://www.mousebearcomedy.com
4017
    name = 'mousebear-tumblr'
4018
    long_name = 'Mouse Bear Comedy (from Tumblr)'
4019
    url = 'http://mousebearcomedy.tumblr.com'
4020
4021
4022
class BouletCorpTumblr(GenericTumblrV1):
4023
    """Class to retrieve BouletCorp comics."""
4024
    # Also on http://www.bouletcorp.com
4025
    name = 'boulet-tumblr'
4026
    long_name = 'Boulet Corp (from Tumblr)'
4027
    url = 'https://bouletcorp.tumblr.com'
4028
    _categories = ('BOULET', )
4029
4030
4031
class TheAwkwardYetiTumblr(GenericTumblrV1):
4032
    """Class to retrieve The Awkward Yeti comics."""
4033
    # Also on http://www.gocomics.com/the-awkward-yeti
4034
    # Also on http://theawkwardyeti.com
4035
    # Also on https://tapastic.com/series/TheAwkwardYeti
4036
    name = 'yeti-tumblr'
4037
    long_name = 'The Awkward Yeti (from Tumblr)'
4038
    url = 'http://larstheyeti.tumblr.com'
4039
    _categories = ('YETI', )
4040
4041
4042
class NellucNhoj(GenericTumblrV1):
4043
    """Class to retrieve NellucNhoj comics."""
4044
    name = 'nhoj'
4045
    long_name = 'Nelluc Nhoj'
4046
    url = 'http://nellucnhoj.com'
4047
4048
4049
class DownTheUpwardSpiralTumblr(GenericTumblrV1):
4050
    """Class to retrieve Down The Upward Spiral comics."""
4051
    # Also on http://www.downtheupwardspiral.com
4052
    # Also on https://tapastic.com/series/Down-the-Upward-Spiral
4053
    name = 'spiral-tumblr'
4054
    long_name = 'Down the Upward Spiral (from Tumblr)'
4055
    url = 'http://downtheupwardspiral.tumblr.com'
4056
4057
4058
class AsPerUsualTumblr(GenericTumblrV1):
4059
    """Class to retrieve As Per Usual comics."""
4060
    # Also on https://tapastic.com/series/AsPerUsual
4061
    name = 'usual-tumblr'
4062
    long_name = 'As Per Usual (from Tumblr)'
4063
    url = 'http://as-per-usual.tumblr.com'
4064
    categories = ('DAMILEE', )
4065
4066
4067
class HotComicsForCoolPeopleTumblr(GenericTumblrV1):
4068
    """Class to retrieve Hot Comics For Cool People."""
4069
    # Also on https://tapastic.com/series/Hot-Comics-For-Cool-People
4070
    # Also on http://hotcomics.biz (links to tumblr)
4071
    # Also on http://hcfcp.com (links to tumblr)
4072
    name = 'hotcomics-tumblr'
4073
    long_name = 'Hot Comics For Cool People (from Tumblr)'
4074
    url = 'http://hotcomicsforcoolpeople.tumblr.com'
4075
    categories = ('DAMILEE', )
4076
4077
4078
class OneOneOneOneComicTumblr(GenericTumblrV1):
4079
    """Class to retrieve 1111 Comics."""
4080
    # Also on http://www.1111comics.me
4081
    # Also on https://tapastic.com/series/1111-Comics
4082
    name = '1111-tumblr'
4083
    long_name = '1111 Comics (from Tumblr)'
4084
    url = 'http://comics1111.tumblr.com'
4085
    _categories = ('ONEONEONEONE', )
4086
4087
4088
class JhallComicsTumblr(GenericTumblrV1):
4089
    """Class to retrieve Jhall Comics."""
4090
    # Also on http://jhallcomics.com
4091
    name = 'jhall-tumblr'
4092
    long_name = 'Jhall Comics (from Tumblr)'
4093
    url = 'http://jhallcomics.tumblr.com'
4094
4095
4096
class BerkeleyMewsTumblr(GenericTumblrV1):
4097
    """Class to retrieve Berkeley Mews comics."""
4098
    # Also on http://www.gocomics.com/berkeley-mews
4099
    # Also on http://www.berkeleymews.com
4100
    name = 'berkeley-tumblr'
4101
    long_name = 'Berkeley Mews (from Tumblr)'
4102
    url = 'http://mews.tumblr.com'
4103
    _categories = ('BERKELEY', )
4104
4105
4106
class JoanCornellaTumblr(GenericTumblrV1):
4107
    """Class to retrieve Joan Cornella comics."""
4108
    # Also on http://joancornella.net
4109
    name = 'cornella-tumblr'
4110
    long_name = 'Joan Cornella (from Tumblr)'
4111
    url = 'http://cornellajoan.tumblr.com'
4112
4113
4114
class RespawnComicTumblr(GenericTumblrV1):
4115
    """Class to retrieve Respawn Comic."""
4116
    # Also on http://respawncomic.com
4117
    name = 'respawn-tumblr'
4118
    long_name = 'Respawn Comic (from Tumblr)'
4119
    url = 'https://respawncomic.tumblr.com'
4120
4121
4122
class ChrisHallbeckTumblr(GenericTumblrV1):
4123
    """Class to retrieve Chris Hallbeck comics."""
4124
    # Also on https://tapastic.com/ChrisHallbeck
4125
    # Also on http://maximumble.com
4126
    # Also on http://minimumble.com
4127
    # Also on http://thebookofbiff.com
4128
    name = 'hallbeck-tumblr'
4129
    long_name = 'Chris Hallback (from Tumblr)'
4130
    url = 'https://chrishallbeck.tumblr.com'
4131
    _categories = ('HALLBACK', )
4132
4133
4134
class ComicNuggets(GenericTumblrV1):
4135
    """Class to retrieve Comic Nuggets."""
4136
    name = 'nuggets'
4137
    long_name = 'Comic Nuggets'
4138
    url = 'http://comicnuggets.com'
4139
4140
4141
class PigeonGazetteTumblr(GenericTumblrV1):
4142
    """Class to retrieve The Pigeon Gazette comics."""
4143
    # Also on https://tapastic.com/series/The-Pigeon-Gazette
4144
    name = 'pigeon-tumblr'
4145
    long_name = 'The Pigeon Gazette (from Tumblr)'
4146
    url = 'http://thepigeongazette.tumblr.com'
4147
4148
4149
class CancerOwl(GenericTumblrV1):
4150
    """Class to retrieve Cancer Owl comics."""
4151
    # Also on http://cancerowl.com
4152
    name = 'cancerowl-tumblr'
4153
    long_name = 'Cancer Owl (from Tumblr)'
4154
    url = 'http://cancerowl.tumblr.com'
4155
4156
4157
class FowlLanguageTumblr(GenericTumblrV1):
4158
    """Class to retrieve Fowl Language comics."""
4159
    # Also on http://www.fowllanguagecomics.com
4160
    # Also on http://tapastic.com/series/Fowl-Language-Comics
4161
    # Also on http://www.gocomics.com/fowl-language
4162
    name = 'fowllanguage-tumblr'
4163
    long_name = 'Fowl Language Comics (from Tumblr)'
4164
    url = 'http://fowllanguagecomics.tumblr.com'
4165
    _categories = ('FOWLLANGUAGE', )
4166
4167
4168
class TheOdd1sOutTumblr(GenericTumblrV1):
4169
    """Class to retrieve The Odd 1s Out comics."""
4170
    # Also on http://theodd1sout.com
4171
    # Also on https://tapastic.com/series/Theodd1sout
4172
    name = 'theodd-tumblr'
4173
    long_name = 'The Odd 1s Out (from Tumblr)'
4174
    url = 'http://theodd1sout.tumblr.com'
4175
4176
4177
class TheUnderfoldTumblr(GenericTumblrV1):
4178
    """Class to retrieve The Underfold comics."""
4179
    # Also on http://theunderfold.com
4180
    name = 'underfold-tumblr'
4181
    long_name = 'The Underfold (from Tumblr)'
4182
    url = 'http://theunderfold.tumblr.com'
4183
4184
4185
class LolNeinTumblr(GenericTumblrV1):
4186
    """Class to retrieve Lol Nein comics."""
4187
    # Also on http://lolnein.com
4188
    name = 'lolnein-tumblr'
4189
    long_name = 'Lol Nein (from Tumblr)'
4190
    url = 'http://lolneincom.tumblr.com'
4191
4192
4193
class FatAwesomeComicsTumblr(GenericTumblrV1):
4194
    """Class to retrieve Fat Awesome Comics."""
4195
    # Also on http://fatawesome.com/comics
4196
    name = 'fatawesome-tumblr'
4197
    long_name = 'Fat Awesome (from Tumblr)'
4198
    url = 'http://fatawesomecomedy.tumblr.com'
4199
4200
4201
class TheWorldIsFlatTumblr(GenericTumblrV1):
4202
    """Class to retrieve The World Is Flat Comics."""
4203
    # Also on https://tapastic.com/series/The-World-is-Flat
4204
    name = 'flatworld-tumblr'
4205
    long_name = 'The World Is Flat (from Tumblr)'
4206
    url = 'http://theworldisflatcomics.com'
4207
4208
4209
class DorrisMc(GenericTumblrV1):
4210
    """Class to retrieve Dorris Mc Comics"""
4211
    # Also on http://www.gocomics.com/dorris-mccomics
4212
    name = 'dorrismc'
4213
    long_name = 'Dorris Mc'
4214
    url = 'http://dorrismccomics.com'
4215
4216
4217
class LeleozTumblr(GenericDeletedComic, GenericTumblrV1):
4218
    """Class to retrieve Leleoz comics."""
4219
    # Also on https://tapastic.com/series/Leleoz
4220
    name = 'leleoz-tumblr'
4221
    long_name = 'Leleoz (from Tumblr)'
4222
    url = 'http://leleozcomics.tumblr.com'
4223
4224
4225
class MoonBeardTumblr(GenericTumblrV1):
4226
    """Class to retrieve MoonBeard comics."""
4227
    # Also on http://moonbeard.com
4228
    # Also on http://www.webtoons.com/en/comedy/moon-beard/list?title_no=471
4229
    name = 'moonbeard-tumblr'
4230
    long_name = 'Moon Beard (from Tumblr)'
4231
    url = 'http://squireseses.tumblr.com'
4232
    _categories = ('MOONBEARD', )
4233
4234
4235
class AComik(GenericTumblrV1):
4236
    """Class to retrieve A Comik"""
4237
    name = 'comik'
4238
    long_name = 'A Comik'
4239
    url = 'http://acomik.com'
4240
4241
4242
class ClassicRandy(GenericTumblrV1):
4243
    """Class to retrieve Classic Randy comics."""
4244
    name = 'randy'
4245
    long_name = 'Classic Randy'
4246
    url = 'http://classicrandy.tumblr.com'
4247
4248
4249
class DagssonTumblr(GenericTumblrV1):
4250
    """Class to retrieve Dagsson comics."""
4251
    # Also on http://www.dagsson.com
4252
    name = 'dagsson-tumblr'
4253
    long_name = 'Dagsson Hugleikur (from Tumblr)'
4254
    url = 'https://hugleikurdagsson.tumblr.com'
4255
4256
4257
class LinsEditionsTumblr(GenericTumblrV1):
4258
    """Class to retrieve L.I.N.S. Editions comics."""
4259
    # Also on https://linsedition.com
4260
    # Now on http://warandpeas.tumblr.com
4261
    name = 'lins-tumblr'
4262
    long_name = 'L.I.N.S. Editions (from Tumblr)'
4263
    url = 'https://linscomics.tumblr.com'
4264
    _categories = ('LINS', )
4265
4266
4267
class WarAndPeasTumblr(GenericTumblrV1):
4268
    """Class to retrieve War And Peas comics."""
4269
    # Was on https://linscomics.tumblr.com
4270
    name = 'warandpeas-tumblr'
4271
    long_name = 'War And Peas (from Tumblr)'
4272
    url = 'http://warandpeas.tumblr.com'
4273
    _categories = ('WARANDPEAS', )
4274
4275
4276
class OrigamiHotDish(GenericTumblrV1):
4277
    """Class to retrieve Origami Hot Dish comics."""
4278
    name = 'origamihotdish'
4279
    long_name = 'Origami Hot Dish'
4280
    url = 'http://origamihotdish.com'
4281
4282
4283
class HitAndMissComicsTumblr(GenericTumblrV1):
4284
    """Class to retrieve Hit and Miss Comics."""
4285
    name = 'hitandmiss'
4286
    long_name = 'Hit and Miss Comics'
4287
    url = 'https://hitandmisscomics.tumblr.com'
4288
4289
4290
class HMBlanc(GenericTumblrV1):
4291
    """Class to retrieve HM Blanc comics."""
4292
    name = 'hmblanc'
4293
    long_name = 'HM Blanc'
4294
    url = 'http://hmblanc.tumblr.com'
4295
4296
4297
class TalesOfAbsurdityTumblr(GenericTumblrV1):
4298
    """Class to retrieve Tales Of Absurdity comics."""
4299
    # Also on http://talesofabsurdity.com
4300
    # Also on http://tapastic.com/series/Tales-Of-Absurdity
4301
    name = 'absurdity-tumblr'
4302
    long_name = 'Tales of Absurdity (from Tumblr)'
4303
    url = 'http://talesofabsurdity.tumblr.com'
4304
    _categories = ('ABSURDITY', )
4305
4306
4307
class RobbieAndBobby(GenericTumblrV1):
4308
    """Class to retrieve Robbie And Bobby comics."""
4309
    # Also on http://robbieandbobby.com
4310
    name = 'robbie-tumblr'
4311
    long_name = 'Robbie And Bobby (from Tumblr)'
4312
    url = 'http://robbieandbobby.tumblr.com'
4313
4314
4315
class ElectricBunnyComicTumblr(GenericTumblrV1):
4316
    """Class to retrieve Electric Bunny Comics."""
4317
    # Also on http://www.electricbunnycomics.com/View/Comic/153/Welcome+to+Hell
4318
    name = 'bunny-tumblr'
4319
    long_name = 'Electric Bunny Comic (from Tumblr)'
4320
    url = 'http://electricbunnycomics.tumblr.com'
4321
4322
4323
class Hoomph(GenericTumblrV1):
4324
    """Class to retrieve Hoomph comics."""
4325
    name = 'hoomph'
4326
    long_name = 'Hoomph'
4327
    url = 'http://hoom.ph'
4328
4329
4330
class BFGFSTumblr(GenericTumblrV1):
4331
    """Class to retrieve BFGFS comics."""
4332
    # Also on https://tapastic.com/series/BFGFS
4333
    # Also on http://bfgfs.com
4334
    name = 'bfgfs-tumblr'
4335
    long_name = 'BFGFS (from Tumblr)'
4336
    url = 'https://bfgfs.tumblr.com'
4337
4338
4339
class DoodleForFood(GenericTumblrV1):
4340
    """Class to retrieve Doodle For Food comics."""
4341
    # Also on https://tapastic.com/series/Doodle-for-Food
4342
    name = 'doodle'
4343
    long_name = 'Doodle For Food'
4344
    url = 'http://www.doodleforfood.com'
4345
4346
4347
class CassandraCalinTumblr(GenericTumblrV1):
4348
    """Class to retrieve C. Cassandra comics."""
4349
    # Also on http://cassandracalin.com
4350
    # Also on https://tapastic.com/series/C-Cassandra-comics
4351
    name = 'cassandra-tumblr'
4352
    long_name = 'Cassandra Calin (from Tumblr)'
4353
    url = 'http://c-cassandra.tumblr.com'
4354
4355
4356
class DougWasTaken(GenericTumblrV1):
4357
    """Class to retrieve Doug Was Taken comics."""
4358
    name = 'doug'
4359
    long_name = 'Doug Was Taken'
4360
    url = 'https://dougwastaken.tumblr.com'
4361
4362
4363
class MandatoryRollerCoaster(GenericTumblrV1):
4364
    """Class to retrieve Mandatory Roller Coaster comics."""
4365
    name = 'rollercoaster'
4366
    long_name = 'Mandatory Roller Coaster'
4367
    url = 'http://mandatoryrollercoaster.com'
4368
4369
4370
class CEstPasEnRegardantSesPompes(GenericTumblrV1):
4371
    """Class to retrieve C'Est Pas En Regardant Ses Pompes (...)  comics."""
4372
    name = 'cperspqccltt'
4373
    long_name = 'C Est Pas En Regardant Ses Pompes (...)'
4374
    url = 'http://marcoandco.tumblr.com'
4375
4376
4377
class TheGrohlTroll(GenericTumblrV1):
4378
    """Class to retrieve The Grohl Troll comics."""
4379
    name = 'grohltroll'
4380
    long_name = 'The Grohl Troll'
4381
    url = 'http://thegrohltroll.com'
4382
4383
4384
class WebcomicName(GenericTumblrV1):
4385
    """Class to retrieve Webcomic Name comics."""
4386
    name = 'webcomicname'
4387
    long_name = 'Webcomic Name'
4388
    url = 'http://webcomicname.com'
4389
4390
4391
class BooksOfAdam(GenericTumblrV1):
4392
    """Class to retrieve Books of Adam comics."""
4393
    # Also on http://www.booksofadam.com
4394
    name = 'booksofadam'
4395
    long_name = 'Books of Adam'
4396
    url = 'http://booksofadam.tumblr.com'
4397
4398
4399
class HarkAVagrant(GenericTumblrV1):
4400
    """Class to retrieve Hark A Vagrant comics."""
4401
    # Also on http://www.harkavagrant.com
4402
    name = 'hark-tumblr'
4403
    long_name = 'Hark A Vagrant (from Tumblr)'
4404
    url = 'http://beatonna.tumblr.com'
4405
4406
4407
class OurSuperAdventureTumblr(GenericTumblrV1):
4408
    """Class to retrieve Our Super Adventure comics."""
4409
    # Also on https://tapastic.com/series/Our-Super-Adventure
4410
    # Also on http://www.oursuperadventure.com
4411
    # http://sarahgraley.com
4412
    name = 'superadventure-tumblr'
4413
    long_name = 'Our Super Adventure (from Tumblr)'
4414
    url = 'http://sarahssketchbook.tumblr.com'
4415
4416
4417
class JakeLikesOnions(GenericTumblrV1):
4418
    """Class to retrieve Jake Likes Onions comics."""
4419
    name = 'jake'
4420
    long_name = 'Jake Likes Onions'
4421
    url = 'http://jakelikesonions.com'
4422
4423
4424
class InYourFaceCakeTumblr(GenericTumblrV1):
4425
    """Class to retrieve In Your Face Cake comics."""
4426
    # Also on https://tapas.io/series/In-Your-Face-Cake
4427
    name = 'inyourfacecake-tumblr'
4428
    long_name = 'In Your Face Cake (from Tumblr)'
4429
    url = 'https://in-your-face-cake.tumblr.com'
4430
    _categories = ('INYOURFACECAKE', )
4431
4432
4433
class Robospunk(GenericTumblrV1):
4434
    """Class to retrieve Robospunk comics."""
4435
    name = 'robospunk'
4436
    long_name = 'Robospunk'
4437
    url = 'http://robospunk.com'
4438
4439
4440
class BananaTwinky(GenericTumblrV1):
4441
    """Class to retrieve Banana Twinky comics."""
4442
    name = 'banana'
4443
    long_name = 'Banana Twinky'
4444
    url = 'https://bananatwinky.tumblr.com'
4445
4446
4447
class YesterdaysPopcornTumblr(GenericTumblrV1):
4448
    """Class to retrieve Yesterday's Popcorn comics."""
4449
    # Also on http://www.yesterdayspopcorn.com
4450
    # Also on https://tapastic.com/series/Yesterdays-Popcorn
4451
    name = 'popcorn-tumblr'
4452
    long_name = 'Yesterday\'s Popcorn (from Tumblr)'
4453
    url = 'http://yesterdayspopcorn.tumblr.com'
4454
4455
4456
class TwistedDoodles(GenericTumblrV1):
4457
    """Class to retrieve Twisted Doodles comics."""
4458
    name = 'twisted'
4459
    long_name = 'Twisted Doodles'
4460
    url = 'http://www.twisteddoodles.com'
4461
4462
4463
class UbertoolTumblr(GenericTumblrV1):
4464
    """Class to retrieve Ubertool comics."""
4465
    # Also on http://ubertoolcomic.com
4466
    # Also on https://tapastic.com/series/ubertool
4467
    name = 'ubertool-tumblr'
4468
    long_name = 'Ubertool (from Tumblr)'
4469
    url = 'https://ubertool.tumblr.com'
4470
    _categories = ('UBERTOOL', )
4471
4472
4473
class LittleLifeLinesTumblr(GenericDeletedComic, GenericTumblrV1):
4474
    """Class to retrieve Little Life Lines comics."""
4475
    # Also on http://www.littlelifelines.com
4476
    name = 'life-tumblr'
4477
    long_name = 'Little Life Lines (from Tumblr)'
4478
    url = 'https://little-life-lines.tumblr.com'
4479
4480
4481
class TheyCanTalk(GenericTumblrV1):
4482
    """Class to retrieve They Can Talk comics."""
4483
    name = 'theycantalk'
4484
    long_name = 'They Can Talk'
4485
    url = 'http://theycantalk.com'
4486
4487
4488
class Will5NeverCome(GenericTumblrV1):
4489
    """Class to retrieve Will 5:00 Never Come comics."""
4490
    name = 'will5'
4491
    long_name = 'Will 5:00 Never Come ?'
4492
    url = 'http://will5nevercome.com'
4493
4494
4495
class Sephko(GenericTumblrV1):
4496
    """Class to retrieve Sephko Comics."""
4497
    # Also on http://www.sephko.com
4498
    name = 'sephko'
4499
    long_name = 'Sephko'
4500
    url = 'https://sephko.tumblr.com'
4501
4502
4503
class BlazersAtDawn(GenericTumblrV1):
4504
    """Class to retrieve Blazers At Dawn Comics."""
4505
    name = 'blazers'
4506
    long_name = 'Blazers At Dawn'
4507
    url = 'http://blazersatdawn.tumblr.com'
4508
4509
4510
class ArtByMoga(GenericEmptyComic, GenericTumblrV1):  # Deactivated because it downloads too many things
4511
    """Class to retrieve Art By Moga Comics."""
4512
    name = 'moga'
4513
    long_name = 'Art By Moga'
4514
    url = 'http://artbymoga.tumblr.com'
4515
4516
4517
class VerbalVomitTumblr(GenericTumblrV1):
4518
    """Class to retrieve Verbal Vomit comics."""
4519
    # Also on http://www.verbal-vomit.com
4520
    name = 'vomit-tumblr'
4521
    long_name = 'Verbal Vomit (from Tumblr)'
4522
    url = 'http://verbalvomits.tumblr.com'
4523
4524
4525
class LibraryComic(GenericTumblrV1):
4526
    """Class to retrieve LibraryComic."""
4527
    # Also on http://librarycomic.com
4528
    name = 'library-tumblr'
4529
    long_name = 'LibraryComic (from Tumblr)'
4530
    url = 'https://librarycomic.tumblr.com'
4531
4532
4533
class TizzyStitchBirdTumblr(GenericTumblrV1):
4534
    """Class to retrieve Tizzy Stitch Bird comics."""
4535
    # Also on http://tizzystitchbird.com
4536
    # Also on https://tapastic.com/series/TizzyStitchbird
4537
    # Also on http://www.webtoons.com/en/challenge/tizzy-stitchbird/list?title_no=50082
4538
    name = 'tizzy-tumblr'
4539
    long_name = 'Tizzy Stitch Bird (from Tumblr)'
4540
    url = 'http://tizzystitchbird.tumblr.com'
4541
4542
4543
class VictimsOfCircumsolarTumblr(GenericTumblrV1):
4544
    """Class to retrieve VictimsOfCircumsolar comics."""
4545
    # Also on http://www.victimsofcircumsolar.com
4546
    name = 'circumsolar-tumblr'
4547
    long_name = 'Victims Of Circumsolar (from Tumblr)'
4548
    url = 'https://victimsofcomics.tumblr.com'
4549
4550
4551
class RockPaperCynicTumblr(GenericTumblrV1):
4552
    """Class to retrieve RockPaperCynic comics."""
4553
    # Also on http://www.rockpapercynic.com
4554
    # Also on https://tapastic.com/series/rockpapercynic
4555
    name = 'rpc-tumblr'
4556
    long_name = 'Rock Paper Cynic (from Tumblr)'
4557
    url = 'http://rockpapercynic.tumblr.com'
4558
4559
4560
class DeadlyPanelTumblr(GenericTumblrV1):
4561
    """Class to retrieve Deadly Panel comics."""
4562
    # Also on http://www.deadlypanel.com
4563
    # Also on https://tapastic.com/series/deadlypanel
4564
    name = 'deadly-tumblr'
4565
    long_name = 'Deadly Panel (from Tumblr)'
4566
    url = 'https://deadlypanel.tumblr.com'
4567
4568
4569
class CatanaComics(GenericComicNotWorking):  # Not a Tumblr anymore ?
4570
    """Class to retrieve Catana comics."""
4571
    name = 'catana'
4572
    long_name = 'Catana'
4573
    url = 'http://www.catanacomics.com'
4574
4575
4576
class AngryAtNothingTumblr(GenericTumblrV1):
4577
    """Class to retrieve Angry at Nothing comics."""
4578
    # Also on http://www.angryatnothing.net
4579
    # Also on http://tapastic.com/series/Comics-yeah-definitely-comics-
4580
    name = 'angry-tumblr'
4581
    long_name = 'Angry At Nothing (from Tumblr)'
4582
    url = 'http://angryatnothing.tumblr.com'
4583
4584
4585
class ShanghaiTango(GenericTumblrV1):
4586
    """Class to retrieve Shanghai Tango comic."""
4587
    name = 'tango'
4588
    long_name = 'Shanghai Tango'
4589
    url = 'http://tango2010weibo.tumblr.com'
4590
4591
4592
class OffTheLeashDogTumblr(GenericTumblrV1):
4593
    """Class to retrieve Off The Leash Dog comics."""
4594
    # Also on http://offtheleashdogcartoons.com
4595
    # Also on http://www.rupertfawcettcartoons.com
4596
    name = 'offtheleash-tumblr'
4597
    long_name = 'Off The Leash Dog (from Tumblr)'
4598
    url = 'http://rupertfawcettsdoggyblog.tumblr.com'
4599
    _categories = ('FAWCETT', )
4600
4601
4602
class ImogenQuestTumblr(GenericTumblrV1):
4603
    """Class to retrieve Imogen Quest comics."""
4604
    # Also on http://imogenquest.net
4605
    name = 'imogen-tumblr'
4606
    long_name = 'Imogen Quest (from Tumblr)'
4607
    url = 'http://imoquest.tumblr.com'
4608
4609
4610
class Shitfest(GenericTumblrV1):
4611
    """Class to retrieve Shitfest comics."""
4612
    name = 'shitfest'
4613
    long_name = 'Shitfest'
4614
    url = 'http://shitfestcomic.com'
4615
4616
4617
class IceCreamSandwichComics(GenericTumblrV1):
4618
    """Class to retrieve Ice Cream Sandwich Comics."""
4619
    name = 'icecream'
4620
    long_name = 'Ice Cream Sandwich Comics'
4621
    url = 'http://icecreamsandwichcomics.com'
4622
4623
4624
class Dustinteractive(GenericTumblrV1):
4625
    """Class to retrieve Dustinteractive comics."""
4626
    name = 'dustinteractive'
4627
    long_name = 'Dustinteractive'
4628
    url = 'http://dustinteractive.com'
4629
4630
4631
class StickyCinemaFloor(GenericTumblrV1):
4632
    """Class to retrieve Sticky Cinema Floor comics."""
4633
    name = 'stickycinema'
4634
    long_name = 'Sticky Cinema Floor'
4635
    url = 'https://stickycinemafloor.tumblr.com'
4636
4637
4638
class IncidentalComicsTumblr(GenericTumblrV1):
4639
    """Class to retrieve Incidental Comics."""
4640
    # Also on http://www.incidentalcomics.com
4641
    name = 'incidental-tumblr'
4642
    long_name = 'Incidental Comics (from Tumblr)'
4643
    url = 'http://incidentalcomics.tumblr.com'
4644
4645
4646
class APleasantWasteOfTimeTumblr(GenericTumblrV1):
4647
    """Class to retrieve A Pleasant Waste Of Time comics."""
4648
    # Also on https://tapas.io/series/A-Pleasant-
4649
    name = 'pleasant-waste-tumblr'
4650
    long_name = 'A Pleasant Waste Of Time (from Tumblr)'
4651
    url = 'https://artjcf.tumblr.com'
4652
    _categories = ('WASTE', )
4653
4654
4655
class HorovitzComicsTumblr(GenericTumblrV1):
4656
    """Class to retrieve Horovitz new comics."""
4657
    # Also on http://www.horovitzcomics.com
4658
    name = 'horovitz-tumblr'
4659
    long_name = 'Horovitz (from Tumblr)'
4660
    url = 'https://horovitzcomics.tumblr.com'
4661
    _categories = ('HOROVITZ', )
4662
4663
4664
class DeepDarkFearsTumblr(GenericTumblrV1):
4665
    """Class to retrieve DeepvDarkvFears comics."""
4666
    name = 'deep-dark-fears-tumblr'
4667
    long_name = 'Deep Dark Fears (from Tumblr)'
4668
    url = 'http://deep-dark-fears.tumblr.com'
4669
4670
4671
class DakotaMcDadzean(GenericTumblrV1):
4672
    """Class to retrieve Dakota McDadzean comics."""
4673
    name = 'dakota'
4674
    long_name = 'Dakota McDadzean'
4675
    url = 'http://dakotamcfadzean.tumblr.com'
4676
4677
4678
class ExtraFabulousComicsTumblr(GenericTumblrV1):
4679
    """Class to retrieve Extra Fabulous Comics."""
4680
    # Also on http://extrafabulouscomics.com
4681
    name = 'efc-tumblr'
4682
    long_name = 'Extra Fabulous Comics (from Tumblr)'
4683
    url = 'https://extrafabulouscomics.tumblr.com'
4684
    _categories = ('EFC', )
4685
4686
4687
class AlexLevesque(GenericTumblrV1):
4688
    """Class to retrieve AlexLevesque comics."""
4689
    name = 'alevesque'
4690
    long_name = 'Alex Levesque'
4691
    url = 'http://alexlevesque.com'
4692
    _categories = ('FRANCAIS', )
4693
4694
4695
class JamesOfNoTradesTumblr(GenericTumblrV1):
4696
    """Class to retrieve JamesOfNoTrades comics."""
4697
    # Also on http://jamesofnotrades.com
4698
    # Also on http://www.webtoons.com/en/challenge/james-of-no-trades/list?title_no=43422
4699
    # Also on https://tapas.io/series/James-of-No-Trades
4700
    name = 'jamesofnotrades-tumblr'
4701
    long_name = 'James Of No Trades (from Tumblr)'
4702
    url = 'http://jamesfregan.tumblr.com'
4703
    _categories = ('JAMESOFNOTRADES', )
4704
4705
4706
class InfiniteGuff(GenericTumblrV1):
4707
    """Class to retrieve Infinite Guff comics."""
4708
    name = 'infiniteguff'
4709
    long_name = 'Infinite Guff'
4710
    url = 'http://infiniteguff.com'
4711
4712
4713
class SkeletonClaw(GenericTumblrV1):
4714
    """Class to retrieve Skeleton Claw comics."""
4715
    name = 'skeletonclaw'
4716
    long_name = 'Skeleton Claw'
4717
    url = 'http://skeletonclaw.com'
4718
4719
4720
class MrsFrolleinTumblr(GenericTumblrV1):
4721
    """Class to retrieve Mrs Frollein comics."""
4722
    # Also on http://www.webtoons.com/en/challenge/mrsfrollein/list?title_no=51710
4723
    name = 'frollein'
4724
    long_name = 'Mrs Frollein (from Tumblr)'
4725
    url = 'https://mrsfrollein.tumblr.com'
4726
4727
4728
class GoodBearComicsTumblr(GenericTumblrV1):
4729
    """Class to retrieve GoodBearComics."""
4730
    # Also on https://goodbearcomics.com
4731
    name = 'goodbear-tumblr'
4732
    long_name = 'Good Bear Comics (from Tumblr)'
4733
    url = 'https://goodbearcomics.tumblr.com'
4734
4735
4736
class BrooklynCartoonsTumblr(GenericTumblrV1):
4737
    """Class to retrieve Brooklyn Cartoons."""
4738
    # Also on https://www.brooklyncartoons.com
4739
    # Also on https://www.instagram.com/brooklyncartoons
4740
    name = 'brooklyn-tumblr'
4741
    long_name = 'Brooklyn Cartoons (from Tumblr)'
4742
    url = 'http://brooklyncartoons.tumblr.com'
4743
4744
4745
class GemmaCorrellTumblr(GenericTumblrV1):
4746
    # Also on http://www.gemmacorrell.com/portfolio/comics/
4747
    name = 'gemma-tumblr'
4748
    long_name = 'Gemma Correll (from Tumblr)'
4749
    url = 'http://gemmacorrell.tumblr.com'
4750
4751
4752
class RobotatertotTumblr(GenericTumblrV1):
4753
    """Class to retrieve Robotatertot comics."""
4754
    # Also on https://www.instagram.com/robotatertotcomics
4755
    name = 'robotatertot-tumblr'
4756
    long_name = 'Robotatertot (from Tumblr)'
4757
    url = 'https://robotatertot.tumblr.com'
4758
4759
4760
class HuffyPenguin(GenericTumblrV1):
4761
    """Class to retrieve Huffy Penguin comics."""
4762
    name = 'huffypenguin'
4763
    long_name = 'Huffy Penguin'
4764
    url = 'http://huffy-penguin.tumblr.com'
4765
4766
4767
class CowardlyComicsTumblr(GenericTumblrV1):
4768
    """Class to retrieve Cowardly Comics."""
4769
    # Also on https://tapas.io/series/CowardlyComics
4770
    # Also on http://www.webtoons.com/en/challenge/cowardly-comics/list?title_no=65893
4771
    name = 'cowardly-tumblr'
4772
    long_name = 'Cowardly Comics (from Tumblr)'
4773
    url = 'http://cowardlycomics.tumblr.com'
4774
4775
4776
class Caw4hwTumblr(GenericTumblrV1):
4777
    """Class to retrieve Caw4hw comics."""
4778
    # Also on https://tapas.io/series/CAW4HW
4779
    name = 'caw4hw-tumblr'
4780
    long_name = 'Caw4hw (from Tumblr)'
4781
    url = 'https://caw4hw.tumblr.com'
4782
4783
4784
class WeFlapsTumblr(GenericTumblrV1):
4785
    """Class to retrieve WeFlaps comics."""
4786
    name = 'weflaps-tumblr'
4787
    long_name = 'We Flaps (from Tumblr)'
4788
    url = 'https://weflaps.tumblr.com'
4789
4790
4791
class TheseInsideJokesTumblr(GenericTumblrV1):
4792
    """Class to retrieve These Inside Jokes comics."""
4793
    # Also on http://www.theseinsidejokes.com
4794
    name = 'theseinsidejokes-tumblr'
4795
    long_name = 'These Inside Jokes (from Tumblr)'
4796
    url = 'http://theseinsidejokes.tumblr.com'
4797
4798
4799
class SinewynTumblr(GenericTumblrV1):
4800
    """Class to retrieve Sinewyn comics."""
4801
    # Also on https://sinewyn.wordpress.com
4802
    name = 'sinewyn-tumblr'
4803
    long_name = 'Sinewyn (from Tumblr)'
4804
    url = 'https://sinewyn.tumblr.com'
4805
4806
4807
class BoumeriesTumblr(GenericTumblrV1):
4808
    """Class to retrieve Boumeries comics."""
4809
    # Also on http://bd.boumerie.com
4810
    # Also on http://comics.boumerie.com
4811
    name = 'boumeries-tumblr'
4812
    long_name = 'Boumeries (from Tumblr)'
4813
    url = 'http://boumeries.tumblr.com/'
4814
    _categories = ('BOUMERIES', )
4815
4816
4817
class HorovitzComics(GenericDeletedComic, GenericListableComic):
4818
    """Generic class to handle the logic common to the different comics from Horovitz."""
4819
    # Also on https://horovitzcomics.tumblr.com
4820
    url = 'http://www.horovitzcomics.com'
4821
    _categories = ('HOROVITZ', )
4822
    img_re = re.compile('.*comics/([0-9]*)/([0-9]*)/([0-9]*)/.*$')
4823
    link_re = NotImplemented
4824
    get_url_from_archive_element = join_cls_url_to_href
4825
4826
    @classmethod
4827
    def get_comic_info(cls, soup, link):
4828
        """Get information about a particular comics."""
4829
        href = link['href']
4830
        num = int(cls.link_re.match(href).groups()[0])
4831
        title = link.string
4832
        imgs = soup.find_all('img', id='comic')
4833
        assert len(imgs) == 1, imgs
4834
        year, month, day = [int(s)
4835
                            for s in cls.img_re.match(imgs[0]['src']).groups()]
4836
        return {
4837
            'title': title,
4838
            'day': day,
4839
            'month': month,
4840
            'year': year,
4841
            'img': [i['src'] for i in imgs],
4842
            'num': num,
4843
        }
4844
4845
    @classmethod
4846
    def get_archive_elements(cls):
4847
        archive_url = 'http://www.horovitzcomics.com/comics/archive/'
4848
        return reversed(get_soup_at_url(archive_url).find_all('a', href=cls.link_re))
4849
4850
4851
class HorovitzNew(HorovitzComics):
4852
    """Class to retrieve Horovitz new comics."""
4853
    name = 'horovitznew'
4854
    long_name = 'Horovitz New'
4855
    link_re = re.compile('^/comics/new/([0-9]+)$')
4856
4857
4858
class HorovitzClassic(HorovitzComics):
4859
    """Class to retrieve Horovitz classic comics."""
4860
    name = 'horovitzclassic'
4861
    long_name = 'Horovitz Classic'
4862
    link_re = re.compile('^/comics/classic/([0-9]+)$')
4863
4864
4865
class GenericGoComic(GenericNavigableComic):
4866
    """Generic class to handle the logic common to comics from gocomics.com."""
4867
    _categories = ('GOCOMIC', )
4868
4869
    @classmethod
4870
    def get_first_comic_link(cls):
4871
        """Get link to first comics."""
4872
        return get_soup_at_url(cls.url).find('a', class_='fa gc-comic-nav__button fa fa-backward sm ')
4873
4874
    @classmethod
4875
    def get_navi_link(cls, last_soup, next_):
4876
        """Get link to next or previous comic."""
4877
        PREV = 'fa gc-comic-nav__button fa-caret-left sm js-previous-comic '
4878
        NEXT = 'fa gc-comic-nav__button fa-caret-right sm js-next-comic d-sm-none '
4879
        return last_soup.find('a', class_=NEXT if next_ else PREV)
4880
4881
    @classmethod
4882
    def get_url_from_link(cls, link):
4883
        gocomics = 'http://www.gocomics.com'
4884
        return urljoin_wrapper(gocomics, link['href'])
4885
4886
    @classmethod
4887
    def get_comic_info(cls, soup, link):
4888
        """Get information about a particular comics."""
4889
        date_str = soup.find('meta', property='article:published_time')['content']
4890
        day = string_to_date(date_str, "%Y-%m-%d")
4891
        imgs = soup.find('picture', class_='img-fluid item-comic-image').find_all('img')
4892
        author = soup.find('meta', property='article:author')['content']
4893
        tags = soup.find('meta', property='article:tag')['content']
4894
        return {
4895
            'day': day.day,
4896
            'month': day.month,
4897
            'year': day.year,
4898
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
4899
            'author': author,
4900
            'tags': tags,
4901
        }
4902
4903
4904
class PearlsBeforeSwine(GenericGoComic):
4905
    """Class to retrieve Pearls Before Swine comics."""
4906
    name = 'pearls'
4907
    long_name = 'Pearls Before Swine'
4908
    url = 'http://www.gocomics.com/pearlsbeforeswine'
4909
4910
4911
class Peanuts(GenericGoComic):
4912
    """Class to retrieve Peanuts comics."""
4913
    name = 'peanuts'
4914
    long_name = 'Peanuts'
4915
    url = 'http://www.gocomics.com/peanuts'
4916
4917
4918
class MattWuerker(GenericGoComic):
4919
    """Class to retrieve Matt Wuerker comics."""
4920
    name = 'wuerker'
4921
    long_name = 'Matt Wuerker'
4922
    url = 'http://www.gocomics.com/mattwuerker'
4923
4924
4925
class TomToles(GenericGoComic):
4926
    """Class to retrieve Tom Toles comics."""
4927
    name = 'toles'
4928
    long_name = 'Tom Toles'
4929
    url = 'http://www.gocomics.com/tomtoles'
4930
4931
4932
class BreakOfDay(GenericGoComic):
4933
    """Class to retrieve Break Of Day comics."""
4934
    name = 'breakofday'
4935
    long_name = 'Break Of Day'
4936
    url = 'http://www.gocomics.com/break-of-day'
4937
4938
4939
class Brevity(GenericGoComic):
4940
    """Class to retrieve Brevity comics."""
4941
    name = 'brevity'
4942
    long_name = 'Brevity'
4943
    url = 'http://www.gocomics.com/brevity'
4944
4945
4946
class MichaelRamirez(GenericGoComic):
4947
    """Class to retrieve Michael Ramirez comics."""
4948
    name = 'ramirez'
4949
    long_name = 'Michael Ramirez'
4950
    url = 'http://www.gocomics.com/michaelramirez'
4951
4952
4953
class MikeLuckovich(GenericGoComic):
4954
    """Class to retrieve Mike Luckovich comics."""
4955
    name = 'luckovich'
4956
    long_name = 'Mike Luckovich'
4957
    url = 'http://www.gocomics.com/mikeluckovich'
4958
4959
4960
class JimBenton(GenericGoComic):
4961
    """Class to retrieve Jim Benton comics."""
4962
    # Also on http://jimbenton.tumblr.com
4963
    name = 'benton'
4964
    long_name = 'Jim Benton'
4965
    url = 'http://www.gocomics.com/jim-benton-cartoons'
4966
4967
4968
class TheArgyleSweater(GenericGoComic):
4969
    """Class to retrieve the Argyle Sweater comics."""
4970
    name = 'argyle'
4971
    long_name = 'Argyle Sweater'
4972
    url = 'http://www.gocomics.com/theargylesweater'
4973
4974
4975
class SunnyStreet(GenericGoComic):
4976
    """Class to retrieve Sunny Street comics."""
4977
    # Also on http://www.sunnystreetcomics.com
4978
    name = 'sunny'
4979
    long_name = 'Sunny Street'
4980
    url = 'http://www.gocomics.com/sunny-street'
4981
4982
4983
class OffTheMark(GenericGoComic):
4984
    """Class to retrieve Off The Mark comics."""
4985
    # Also on https://www.offthemark.com
4986
    name = 'offthemark'
4987
    long_name = 'Off The Mark'
4988
    url = 'http://www.gocomics.com/offthemark'
4989
4990
4991
class WuMo(GenericGoComic):
4992
    """Class to retrieve WuMo comics."""
4993
    # Also on http://wumo.com
4994
    name = 'wumo'
4995
    long_name = 'WuMo'
4996
    url = 'http://www.gocomics.com/wumo'
4997
4998
4999
class LunarBaboon(GenericGoComic):
5000
    """Class to retrieve Lunar Baboon comics."""
5001
    # Also on http://www.lunarbaboon.com
5002
    # Also on https://tapastic.com/series/Lunarbaboon
5003
    name = 'lunarbaboon'
5004
    long_name = 'Lunar Baboon'
5005
    url = 'http://www.gocomics.com/lunarbaboon'
5006
5007
5008
class SandersenGocomic(GenericGoComic):
5009
    """Class to retrieve Sarah Andersen comics."""
5010
    # Also on http://sarahcandersen.com
5011
    # Also on http://tapastic.com/series/Doodle-Time
5012
    name = 'sandersen-goc'
5013
    long_name = 'Sarah Andersen (from GoComics)'
5014
    url = 'http://www.gocomics.com/sarahs-scribbles'
5015
5016
5017
class SaturdayMorningBreakfastCerealGoComic(GenericGoComic):
5018
    """Class to retrieve Saturday Morning Breakfast Cereal comics."""
5019
    # Also on http://smbc-comics.tumblr.com
5020
    # Also on http://www.smbc-comics.com
5021
    name = 'smbc-goc'
5022
    long_name = 'Saturday Morning Breakfast Cereal (from GoComics)'
5023
    url = 'http://www.gocomics.com/saturday-morning-breakfast-cereal'
5024
    _categories = ('SMBC', )
5025
5026
5027
class CalvinAndHobbesGoComic(GenericGoComic):
5028
    """Class to retrieve Calvin and Hobbes comics."""
5029
    # From gocomics, not http://marcel-oehler.marcellosendos.ch/comics/ch/
5030
    name = 'calvin-goc'
5031
    long_name = 'Calvin and Hobbes (from GoComics)'
5032
    url = 'http://www.gocomics.com/calvinandhobbes'
5033
5034
5035
class RallGoComic(GenericGoComic):
5036
    """Class to retrieve Ted Rall comics."""
5037
    # Also on http://rall.com/comic
5038
    name = 'rall-goc'
5039
    long_name = "Ted Rall (from GoComics)"
5040
    url = "http://www.gocomics.com/ted-rall"
5041
    _categories = ('RALL', )
5042
5043
5044
class TheAwkwardYetiGoComic(GenericGoComic):
5045
    """Class to retrieve The Awkward Yeti comics."""
5046
    # Also on http://larstheyeti.tumblr.com
5047
    # Also on http://theawkwardyeti.com
5048
    # Also on https://tapastic.com/series/TheAwkwardYeti
5049
    name = 'yeti-goc'
5050
    long_name = 'The Awkward Yeti (from GoComics)'
5051
    url = 'http://www.gocomics.com/the-awkward-yeti'
5052
    _categories = ('YETI', )
5053
5054
5055
class BerkeleyMewsGoComics(GenericGoComic):
5056
    """Class to retrieve Berkeley Mews comics."""
5057
    # Also on http://mews.tumblr.com
5058
    # Also on http://www.berkeleymews.com
5059
    name = 'berkeley-goc'
5060
    long_name = 'Berkeley Mews (from GoComics)'
5061
    url = 'http://www.gocomics.com/berkeley-mews'
5062
    _categories = ('BERKELEY', )
5063
5064
5065
class SheldonGoComics(GenericGoComic):
5066
    """Class to retrieve Sheldon comics."""
5067
    # Also on http://www.sheldoncomics.com
5068
    name = 'sheldon-goc'
5069
    long_name = 'Sheldon Comics (from GoComics)'
5070
    url = 'http://www.gocomics.com/sheldon'
5071
5072
5073
class FowlLanguageGoComics(GenericGoComic):
5074
    """Class to retrieve Fowl Language comics."""
5075
    # Also on http://www.fowllanguagecomics.com
5076
    # Also on http://tapastic.com/series/Fowl-Language-Comics
5077
    # Also on http://fowllanguagecomics.tumblr.com
5078
    name = 'fowllanguage-goc'
5079
    long_name = 'Fowl Language Comics (from GoComics)'
5080
    url = 'http://www.gocomics.com/fowl-language'
5081
    _categories = ('FOWLLANGUAGE', )
5082
5083
5084
class NickAnderson(GenericGoComic):
5085
    """Class to retrieve Nick Anderson comics."""
5086
    name = 'nickanderson'
5087
    long_name = 'Nick Anderson'
5088
    url = 'http://www.gocomics.com/nickanderson'
5089
5090
5091
class GarfieldGoComics(GenericGoComic):
5092
    """Class to retrieve Garfield comics."""
5093
    # Also on http://garfield.com
5094
    name = 'garfield-goc'
5095
    long_name = 'Garfield (from GoComics)'
5096
    url = 'http://www.gocomics.com/garfield'
5097
    _categories = ('GARFIELD', )
5098
5099
5100
class DorrisMcGoComics(GenericGoComic):
5101
    """Class to retrieve Dorris Mc Comics"""
5102
    # Also on http://dorrismccomics.com
5103
    name = 'dorrismc-goc'
5104
    long_name = 'Dorris Mc (from GoComics)'
5105
    url = 'http://www.gocomics.com/dorris-mccomics'
5106
5107
5108
class FoxTrot(GenericGoComic):
5109
    """Class to retrieve FoxTrot comics."""
5110
    name = 'foxtrot'
5111
    long_name = 'FoxTrot'
5112
    url = 'http://www.gocomics.com/foxtrot'
5113
5114
5115
class FoxTrotClassics(GenericGoComic):
5116
    """Class to retrieve FoxTrot Classics comics."""
5117
    name = 'foxtrot-classics'
5118
    long_name = 'FoxTrot Classics'
5119
    url = 'http://www.gocomics.com/foxtrotclassics'
5120
5121
5122
class MisterAndMeGoComics(GenericDeletedComic, GenericGoComic):
5123
    """Class to retrieve Mister & Me Comics."""
5124
    # Also on http://www.mister-and-me.com
5125
    # Also on https://tapastic.com/series/Mister-and-Me
5126
    name = 'mister-goc'
5127
    long_name = 'Mister & Me (from GoComics)'
5128
    url = 'http://www.gocomics.com/mister-and-me'
5129
5130
5131
class NonSequitur(GenericGoComic):
5132
    """Class to retrieve Non Sequitur (Wiley Miller) comics."""
5133
    name = 'nonsequitur'
5134
    long_name = 'Non Sequitur'
5135
    url = 'http://www.gocomics.com/nonsequitur'
5136
5137
5138
class JoeyAlisonSayers(GenericGoComic):
5139
    """Class to retrieve Joey Alison Sayers comics."""
5140
    name = 'joeyalison'
5141
    long_name = 'Joey Alison Sayers (from GoComics)'
5142
    url = 'http://www.gocomics.com/joey-alison-sayers-comics'
5143
5144
5145
class SavageChickenGoComics(GenericGoComic):
5146
    """Class to retrieve Savage Chicken comics."""
5147
    # Also on http://www.savagechickens.com
5148
    name = 'savage-goc'
5149
    long_name = 'Savage Chicken (from GoComics)'
5150
    url = 'http://www.gocomics.com/savage-chickens'
5151
5152
5153
class GenericTapasticComic(GenericListableComic):
5154
    """Generic class to handle the logic common to comics from tapastic.com."""
5155
    _categories = ('TAPASTIC', )
5156
5157
    @classmethod
5158
    def get_comic_info(cls, soup, archive_elt):
5159
        """Get information about a particular comics."""
5160
        timestamp = int(archive_elt['publishDate']) / 1000.0
5161
        day = datetime.datetime.fromtimestamp(timestamp).date()
5162
        imgs = soup.find_all('img', class_='art-image')
5163
        if not imgs:
5164
            # print("Comic %s is being uploaded, retry later" % cls.get_url_from_archive_element(archive_elt))
5165
            return None
5166
        assert len(imgs) > 0, imgs
5167
        return {
5168
            'day': day.day,
5169
            'year': day.year,
5170
            'month': day.month,
5171
            'img': [i['src'] for i in imgs],
5172
            'title': archive_elt['title'],
5173
        }
5174
5175
    @classmethod
5176
    def get_url_from_archive_element(cls, archive_elt):
5177
        return 'http://tapastic.com/episode/' + str(archive_elt['id'])
5178
5179
    @classmethod
5180
    def get_archive_elements(cls):
5181
        pref, suff = 'episodeList : ', ','
5182
        # Information is stored in the javascript part
5183
        # I don't know the clean way to get it so this is the ugly way.
5184
        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]
5185
        return json.loads(string)
5186
5187
5188
class VegetablesForDessert(GenericTapasticComic):
5189
    """Class to retrieve Vegetables For Dessert comics."""
5190
    # Also on http://vegetablesfordessert.tumblr.com
5191
    name = 'vegetables'
5192
    long_name = 'Vegetables For Dessert'
5193
    url = 'http://tapastic.com/series/vegetablesfordessert'
5194
5195
5196
class FowlLanguageTapa(GenericTapasticComic):
5197
    """Class to retrieve Fowl Language comics."""
5198
    # Also on http://www.fowllanguagecomics.com
5199
    # Also on http://fowllanguagecomics.tumblr.com
5200
    # Also on http://www.gocomics.com/fowl-language
5201
    name = 'fowllanguage-tapa'
5202
    long_name = 'Fowl Language Comics (from Tapastic)'
5203
    url = 'http://tapastic.com/series/Fowl-Language-Comics'
5204
    _categories = ('FOWLLANGUAGE', )
5205
5206
5207
class OscillatingProfundities(GenericTapasticComic):
5208
    """Class to retrieve Oscillating Profundities comics."""
5209
    name = 'oscillating'
5210
    long_name = 'Oscillating Profundities'
5211
    url = 'http://tapastic.com/series/oscillatingprofundities'
5212
5213
5214
class ZnoflatsComics(GenericTapasticComic):
5215
    """Class to retrieve Znoflats comics."""
5216
    name = 'znoflats'
5217
    long_name = 'Znoflats Comics'
5218
    url = 'http://tapastic.com/series/Znoflats-Comics'
5219
5220
5221
class SandersenTapastic(GenericTapasticComic):
5222
    """Class to retrieve Sarah Andersen comics."""
5223
    # Also on http://sarahcandersen.com
5224
    # Also on http://www.gocomics.com/sarahs-scribbles
5225
    name = 'sandersen-tapa'
5226
    long_name = 'Sarah Andersen (from Tapastic)'
5227
    url = 'http://tapastic.com/series/Doodle-Time'
5228
5229
5230
class TubeyToonsTapastic(GenericTapasticComic):
5231
    """Class to retrieve TubeyToons comics."""
5232
    # Also on http://tubeytoons.com
5233
    # Also on https://tubeytoons.tumblr.com
5234
    name = 'tubeytoons-tapa'
5235
    long_name = 'Tubey Toons (from Tapastic)'
5236
    url = 'http://tapastic.com/series/Tubey-Toons'
5237
    _categories = ('TUNEYTOONS', )
5238
5239
5240
class AnythingComicTapastic(GenericTapasticComic):
5241
    """Class to retrieve Anything Comics."""
5242
    # Also on http://www.anythingcomic.com
5243
    name = 'anythingcomic-tapa'
5244
    long_name = 'Anything Comic (from Tapastic)'
5245
    url = 'http://tapastic.com/series/anything'
5246
5247
5248
class UnearthedComicsTapastic(GenericTapasticComic):
5249
    """Class to retrieve Unearthed comics."""
5250
    # Also on http://unearthedcomics.com
5251
    # Also on https://unearthedcomics.tumblr.com
5252
    name = 'unearthed-tapa'
5253
    long_name = 'Unearthed Comics (from Tapastic)'
5254
    url = 'http://tapastic.com/series/UnearthedComics'
5255
    _categories = ('UNEARTHED', )
5256
5257
5258
class EverythingsStupidTapastic(GenericTapasticComic):
5259
    """Class to retrieve Everything's stupid Comics."""
5260
    # Also on http://www.webtoons.com/en/challenge/everythings-stupid/list?title_no=14591
5261
    # Also on http://everythingsstupid.net
5262
    name = 'stupid-tapa'
5263
    long_name = "Everything's Stupid (from Tapastic)"
5264
    url = 'http://tapastic.com/series/EverythingsStupid'
5265
5266
5267
class JustSayEhTapastic(GenericTapasticComic):
5268
    """Class to retrieve Just Say Eh comics."""
5269
    # Also on http://www.justsayeh.com
5270
    name = 'justsayeh-tapa'
5271
    long_name = 'Just Say Eh (from Tapastic)'
5272
    url = 'http://tapastic.com/series/Just-Say-Eh'
5273
5274
5275
class ThorsThundershackTapastic(GenericTapasticComic):
5276
    """Class to retrieve Thor's Thundershack comics."""
5277
    # Also on http://www.thorsthundershack.com
5278
    name = 'thor-tapa'
5279
    long_name = 'Thor\'s Thundershack (from Tapastic)'
5280
    url = 'http://tapastic.com/series/Thors-Thundershac'
5281
    _categories = ('THOR', )
5282
5283
5284
class OwlTurdTapastic(GenericTapasticComic):
5285
    """Class to retrieve Owl Turd comics."""
5286
    # Also on http://owlturd.com
5287
    name = 'owlturd-tapa'
5288
    long_name = 'Owl Turd (from Tapastic)'
5289
    url = 'http://tapastic.com/series/Owl-Turd-Comix'
5290
    _categories = ('OWLTURD', )
5291
5292
5293
class GoneIntoRaptureTapastic(GenericTapasticComic):
5294
    """Class to retrieve Gone Into Rapture comics."""
5295
    # Also on http://goneintorapture.tumblr.com
5296
    # Also on http://goneintorapture.com
5297
    name = 'rapture-tapa'
5298
    long_name = 'Gone Into Rapture (from Tapastic)'
5299
    url = 'http://tapastic.com/series/Goneintorapture'
5300
5301
5302
class HeckIfIKnowComicsTapa(GenericTapasticComic):
5303
    """Class to retrieve Heck If I Know Comics."""
5304
    # Also on http://heckifiknowcomics.com
5305
    name = 'heck-tapa'
5306
    long_name = 'Heck if I Know comics (from Tapastic)'
5307
    url = 'http://tapastic.com/series/Regular'
5308
5309
5310
class CheerUpEmoKidTapa(GenericTapasticComic):
5311
    """Class to retrieve CheerUpEmoKid comics."""
5312
    # Also on http://www.cheerupemokid.com
5313
    # Also on https://enzocomics.tumblr.com
5314
    name = 'cuek-tapa'
5315
    long_name = 'Cheer Up Emo Kid (from Tapastic)'
5316
    url = 'http://tapastic.com/series/CUEK'
5317
5318
5319
class BigFootJusticeTapa(GenericTapasticComic):
5320
    """Class to retrieve Big Foot Justice comics."""
5321
    # Also on http://bigfootjustice.com
5322
    name = 'bigfoot-tapa'
5323
    long_name = 'Big Foot Justice (from Tapastic)'
5324
    url = 'http://tapastic.com/series/bigfoot-justice'
5325
5326
5327
class UpAndOutTapa(GenericTapasticComic):
5328
    """Class to retrieve Up & Out comics."""
5329
    # Also on http://upandoutcomic.tumblr.com
5330
    name = 'upandout-tapa'
5331
    long_name = 'Up And Out (from Tapastic)'
5332
    url = 'http://tapastic.com/series/UP-and-OUT'
5333
5334
5335
class ToonHoleTapa(GenericTapasticComic):
5336
    """Class to retrieve Toon Holes comics."""
5337
    # Also on http://www.toonhole.com
5338
    name = 'toonhole-tapa'
5339
    long_name = 'Toon Hole (from Tapastic)'
5340
    url = 'http://tapastic.com/series/TOONHOLE'
5341
5342
5343
class AngryAtNothingTapa(GenericTapasticComic):
5344
    """Class to retrieve Angry at Nothing comics."""
5345
    # Also on http://www.angryatnothing.net
5346
    # Also on http://angryatnothing.tumblr.com
5347
    name = 'angry-tapa'
5348
    long_name = 'Angry At Nothing (from Tapastic)'
5349
    url = 'http://tapastic.com/series/Comics-yeah-definitely-comics-'
5350
5351
5352
class LeleozTapa(GenericTapasticComic):
5353
    """Class to retrieve Leleoz comics."""
5354
    # Also on http://leleozcomics.tumblr.com
5355
    name = 'leleoz-tapa'
5356
    long_name = 'Leleoz (from Tapastic)'
5357
    url = 'https://tapastic.com/series/Leleoz'
5358
5359
5360
class TheAwkwardYetiTapa(GenericTapasticComic):
5361
    """Class to retrieve The Awkward Yeti comics."""
5362
    # Also on http://www.gocomics.com/the-awkward-yeti
5363
    # Also on http://theawkwardyeti.com
5364
    # Also on http://larstheyeti.tumblr.com
5365
    name = 'yeti-tapa'
5366
    long_name = 'The Awkward Yeti (from Tapastic)'
5367
    url = 'https://tapastic.com/series/TheAwkwardYeti'
5368
    _categories = ('YETI', )
5369
5370
5371
class AsPerUsualTapa(GenericTapasticComic):
5372
    """Class to retrieve As Per Usual comics."""
5373
    # Also on http://as-per-usual.tumblr.com
5374
    name = 'usual-tapa'
5375
    long_name = 'As Per Usual (from Tapastic)'
5376
    url = 'https://tapastic.com/series/AsPerUsual'
5377
    categories = ('DAMILEE', )
5378
5379
5380
class HotComicsForCoolPeopleTapa(GenericTapasticComic):
5381
    """Class to retrieve Hot Comics For Cool People."""
5382
    # Also on http://hotcomicsforcoolpeople.tumblr.com
5383
    # Also on http://hotcomics.biz (links to tumblr)
5384
    # Also on http://hcfcp.com (links to tumblr)
5385
    name = 'hotcomics-tapa'
5386
    long_name = 'Hot Comics For Cool People (from Tapastic)'
5387
    url = 'https://tapastic.com/series/Hot-Comics-For-Cool-People'
5388
    categories = ('DAMILEE', )
5389
5390
5391
class OneOneOneOneComicTapa(GenericTapasticComic):
5392
    """Class to retrieve 1111 Comics."""
5393
    # Also on http://www.1111comics.me
5394
    # Also on http://comics1111.tumblr.com
5395
    name = '1111-tapa'
5396
    long_name = '1111 Comics (from Tapastic)'
5397
    url = 'https://tapastic.com/series/1111-Comics'
5398
    _categories = ('ONEONEONEONE', )
5399
5400
5401
class TumbleDryTapa(GenericTapasticComic):
5402
    """Class to retrieve Tumble Dry comics."""
5403
    # Also on http://tumbledrycomics.com
5404
    name = 'tumbledry-tapa'
5405
    long_name = 'Tumblr Dry (from Tapastic)'
5406
    url = 'https://tapastic.com/series/TumbleDryComics'
5407
5408
5409
class DeadlyPanelTapa(GenericTapasticComic):
5410
    """Class to retrieve Deadly Panel comics."""
5411
    # Also on http://www.deadlypanel.com
5412
    # Also on https://deadlypanel.tumblr.com
5413
    name = 'deadly-tapa'
5414
    long_name = 'Deadly Panel (from Tapastic)'
5415
    url = 'https://tapastic.com/series/deadlypanel'
5416
5417
5418
class ChrisHallbeckMaxiTapa(GenericTapasticComic):
5419
    """Class to retrieve Chris Hallbeck comics."""
5420
    # Also on https://chrishallbeck.tumblr.com
5421
    # Also on http://maximumble.com
5422
    name = 'hallbeckmaxi-tapa'
5423
    long_name = 'Chris Hallback - Maximumble (from Tapastic)'
5424
    url = 'https://tapastic.com/series/Maximumble'
5425
    _categories = ('HALLBACK', )
5426
5427
5428
class ChrisHallbeckMiniTapa(GenericDeletedComic, GenericTapasticComic):
5429
    """Class to retrieve Chris Hallbeck comics."""
5430
    # Also on https://chrishallbeck.tumblr.com
5431
    # Also on http://minimumble.com
5432
    name = 'hallbeckmini-tapa'
5433
    long_name = 'Chris Hallback - Minimumble (from Tapastic)'
5434
    url = 'https://tapastic.com/series/Minimumble'
5435
    _categories = ('HALLBACK', )
5436
5437
5438
class ChrisHallbeckBiffTapa(GenericDeletedComic, GenericTapasticComic):
5439
    """Class to retrieve Chris Hallbeck comics."""
5440
    # Also on https://chrishallbeck.tumblr.com
5441
    # Also on http://thebookofbiff.com
5442
    name = 'hallbeckbiff-tapa'
5443
    long_name = 'Chris Hallback - The Book of Biff (from Tapastic)'
5444
    url = 'https://tapastic.com/series/Biff'
5445
    _categories = ('HALLBACK', )
5446
5447
5448
class RandoWisTapa(GenericTapasticComic):
5449
    """Class to retrieve RandoWis comics."""
5450
    # Also on https://randowis.com
5451
    name = 'randowis-tapa'
5452
    long_name = 'RandoWis (from Tapastic)'
5453
    url = 'https://tapastic.com/series/RandoWis'
5454
5455
5456
class PigeonGazetteTapa(GenericTapasticComic):
5457
    """Class to retrieve The Pigeon Gazette comics."""
5458
    # Also on http://thepigeongazette.tumblr.com
5459
    name = 'pigeon-tapa'
5460
    long_name = 'The Pigeon Gazette (from Tapastic)'
5461
    url = 'https://tapastic.com/series/The-Pigeon-Gazette'
5462
5463
5464
class TheOdd1sOutTapa(GenericTapasticComic):
5465
    """Class to retrieve The Odd 1s Out comics."""
5466
    # Also on http://theodd1sout.com
5467
    # Also on http://theodd1sout.tumblr.com
5468
    name = 'theodd-tapa'
5469
    long_name = 'The Odd 1s Out (from Tapastic)'
5470
    url = 'https://tapastic.com/series/Theodd1sout'
5471
5472
5473
class TheWorldIsFlatTapa(GenericTapasticComic):
5474
    """Class to retrieve The World Is Flat Comics."""
5475
    # Also on http://theworldisflatcomics.tumblr.com
5476
    name = 'flatworld-tapa'
5477
    long_name = 'The World Is Flat (from Tapastic)'
5478
    url = 'https://tapastic.com/series/The-World-is-Flat'
5479
5480
5481
class MisterAndMeTapa(GenericTapasticComic):
5482
    """Class to retrieve Mister & Me Comics."""
5483
    # Also on http://www.mister-and-me.com
5484
    # Also on http://www.gocomics.com/mister-and-me
5485
    name = 'mister-tapa'
5486
    long_name = 'Mister & Me (from Tapastic)'
5487
    url = 'https://tapastic.com/series/Mister-and-Me'
5488
5489
5490
class TalesOfAbsurdityTapa(GenericDeletedComic, GenericTapasticComic):
5491
    """Class to retrieve Tales Of Absurdity comics."""
5492
    # Also on http://talesofabsurdity.com
5493
    # Also on http://talesofabsurdity.tumblr.com
5494
    name = 'absurdity-tapa'
5495
    long_name = 'Tales of Absurdity (from Tapastic)'
5496
    url = 'http://tapastic.com/series/Tales-Of-Absurdity'
5497
    _categories = ('ABSURDITY', )
5498
5499
5500
class BFGFSTapa(GenericTapasticComic):
5501
    """Class to retrieve BFGFS comics."""
5502
    # Also on http://bfgfs.com
5503
    # Also on https://bfgfs.tumblr.com
5504
    name = 'bfgfs-tapa'
5505
    long_name = 'BFGFS (from Tapastic)'
5506
    url = 'https://tapastic.com/series/BFGFS'
5507
5508
5509
class DoodleForFoodTapa(GenericTapasticComic):
5510
    """Class to retrieve Doodle For Food comics."""
5511
    # Also on http://www.doodleforfood.com
5512
    name = 'doodle-tapa'
5513
    long_name = 'Doodle For Food (from Tapastic)'
5514
    url = 'https://tapastic.com/series/Doodle-for-Food'
5515
5516
5517
class MrLovensteinTapa(GenericTapasticComic):
5518
    """Class to retrieve Mr Lovenstein comics."""
5519
    # Also on  https://tapastic.com/series/MrLovenstein
5520
    name = 'mrlovenstein-tapa'
5521
    long_name = 'Mr. Lovenstein (from Tapastic)'
5522
    url = 'https://tapastic.com/series/MrLovenstein'
5523
5524
5525
class CassandraCalinTapa(GenericTapasticComic):
5526
    """Class to retrieve C. Cassandra comics."""
5527
    # Also on http://cassandracalin.com
5528
    # Also on http://c-cassandra.tumblr.com
5529
    name = 'cassandra-tapa'
5530
    long_name = 'Cassandra Calin (from Tapastic)'
5531
    url = 'https://tapastic.com/series/C-Cassandra-comics'
5532
5533
5534
class WafflesAndPancakes(GenericTapasticComic):
5535
    """Class to retrieve Waffles And Pancakes comics."""
5536
    # Also on http://wandpcomic.com
5537
    name = 'waffles'
5538
    long_name = 'Waffles And Pancakes'
5539
    url = 'https://tapastic.com/series/Waffles-and-Pancakes'
5540
5541
5542
class YesterdaysPopcornTapastic(GenericTapasticComic):
5543
    """Class to retrieve Yesterday's Popcorn comics."""
5544
    # Also on http://www.yesterdayspopcorn.com
5545
    # Also on http://yesterdayspopcorn.tumblr.com
5546
    name = 'popcorn-tapa'
5547
    long_name = 'Yesterday\'s Popcorn (from Tapastic)'
5548
    url = 'https://tapastic.com/series/Yesterdays-Popcorn'
5549
5550
5551
class OurSuperAdventureTapastic(GenericDeletedComic, GenericTapasticComic):
5552
    """Class to retrieve Our Super Adventure comics."""
5553
    # Also on http://www.oursuperadventure.com
5554
    # http://sarahssketchbook.tumblr.com
5555
    # http://sarahgraley.com
5556
    name = 'superadventure-tapastic'
5557
    long_name = 'Our Super Adventure (from Tapastic)'
5558
    url = 'https://tapastic.com/series/Our-Super-Adventure'
5559
5560
5561
class NamelessPCs(GenericTapasticComic):
5562
    """Class to retrieve Nameless PCs comics."""
5563
    # Also on http://namelesspcs.com
5564
    name = 'namelesspcs-tapa'
5565
    long_name = 'NamelessPCs (from Tapastic)'
5566
    url = 'https://tapastic.com/series/NamelessPC'
5567
5568
5569
class DownTheUpwardSpiralTapa(GenericTapasticComic):
5570
    """Class to retrieve Down The Upward Spiral comics."""
5571
    # Also on http://www.downtheupwardspiral.com
5572
    # Also on http://downtheupwardspiral.tumblr.com
5573
    name = 'spiral-tapa'
5574
    long_name = 'Down the Upward Spiral (from Tapastic)'
5575
    url = 'https://tapastic.com/series/Down-the-Upward-Spiral'
5576
5577
5578
class UbertoolTapa(GenericTapasticComic):
5579
    """Class to retrieve Ubertool comics."""
5580
    # Also on http://ubertoolcomic.com
5581
    # Also on https://ubertool.tumblr.com
5582
    name = 'ubertool-tapa'
5583
    long_name = 'Ubertool (from Tapastic)'
5584
    url = 'https://tapastic.com/series/ubertool'
5585
    _categories = ('UBERTOOL', )
5586
5587
5588
class BarteNerdsTapa(GenericDeletedComic, GenericTapasticComic):
5589
    """Class to retrieve BarteNerds comics."""
5590
    # Also on http://www.bartenerds.com
5591
    name = 'bartenerds-tapa'
5592
    long_name = 'BarteNerds (from Tapastic)'
5593
    url = 'https://tapastic.com/series/BarteNERDS'
5594
5595
5596
class SmallBlueYonderTapa(GenericTapasticComic):
5597
    """Class to retrieve Small Blue Yonder comics."""
5598
    # Also on http://www.smallblueyonder.com
5599
    name = 'smallblue-tapa'
5600
    long_name = 'Small Blue Yonder (from Tapastic)'
5601
    url = 'https://tapastic.com/series/Small-Blue-Yonder'
5602
5603
5604
class TizzyStitchBirdTapa(GenericTapasticComic):
5605
    """Class to retrieve Tizzy Stitch Bird comics."""
5606
    # Also on http://tizzystitchbird.com
5607
    # Also on http://tizzystitchbird.tumblr.com
5608
    # Also on http://www.webtoons.com/en/challenge/tizzy-stitchbird/list?title_no=50082
5609
    name = 'tizzy-tapa'
5610
    long_name = 'Tizzy Stitch Bird (from Tapastic)'
5611
    url = 'https://tapastic.com/series/TizzyStitchbird'
5612
5613
5614
class RockPaperCynicTapa(GenericTapasticComic):
5615
    """Class to retrieve RockPaperCynic comics."""
5616
    # Also on http://www.rockpapercynic.com
5617
    # Also on http://rockpapercynic.tumblr.com
5618
    name = 'rpc-tapa'
5619
    long_name = 'Rock Paper Cynic (from Tapastic)'
5620
    url = 'https://tapastic.com/series/rockpapercynic'
5621
5622
5623
class IsItCanonTapa(GenericTapasticComic):
5624
    """Class to retrieve Is It Canon comics."""
5625
    # Also on http://www.isitcanon.com
5626
    name = 'canon-tapa'
5627
    long_name = 'Is It Canon (from Tapastic)'
5628
    url = 'http://tapastic.com/series/isitcanon'
5629
5630
5631
class ItsTheTieTapa(GenericTapasticComic):
5632
    """Class to retrieve It's the tie comics."""
5633
    # Also on http://itsthetie.com
5634
    # Also on http://itsthetie.tumblr.com
5635
    name = 'tie-tapa'
5636
    long_name = "It's the tie (from Tapastic)"
5637
    url = "https://tapastic.com/series/itsthetie"
5638
    _categories = ('TIE', )
5639
5640
5641
class JamesOfNoTradesTapa(GenericTapasticComic):
5642
    """Class to retrieve JamesOfNoTrades comics."""
5643
    # Also on http://jamesofnotrades.com
5644
    # Also on http://www.webtoons.com/en/challenge/james-of-no-trades/list?title_no=43422
5645
    # Also on http://jamesfregan.tumblr.com
5646
    name = 'jamesofnotrades-tapa'
5647
    long_name = 'James Of No Trades (from Tapastic)'
5648
    url = 'https://tapas.io/series/James-of-No-Trades'
5649
    _categories = ('JAMESOFNOTRADES', )
5650
5651
5652
class MomentumTapa(GenericTapasticComic):
5653
    """Class to retrieve Momentum comics."""
5654
    # Also on http://www.momentumcomic.com
5655
    name = 'momentum-tapa'
5656
    long_name = 'Momentum (from Tapastic)'
5657
    url = 'https://tapastic.com/series/momentum'
5658
5659
5660
class InYourFaceCakeTapa(GenericTapasticComic):
5661
    """Class to retrieve In Your Face Cake comics."""
5662
    # Also on https://in-your-face-cake.tumblr.com
5663
    name = 'inyourfacecake-tapa'
5664
    long_name = 'In Your Face Cake (from Tapastic)'
5665
    url = 'https://tapas.io/series/In-Your-Face-Cake'
5666
    _categories = ('INYOURFACECAKE', )
5667
5668
5669
class CowardlyComicsTapa(GenericTapasticComic):
5670
    """Class to retrieve Cowardly Comics."""
5671
    # Also on http://cowardlycomics.tumblr.com
5672
    # Also on http://www.webtoons.com/en/challenge/cowardly-comics/list?title_no=65893
5673
    name = 'cowardly-tapa'
5674
    long_name = 'Cowardly Comics (from Tapastic)'
5675
    url = 'https://tapas.io/series/CowardlyComics'
5676
5677
5678
class Caw4hwTapa(GenericTapasticComic):
5679
    """Class to retrieve Caw4hw comics."""
5680
    # Also on https://caw4hw.tumblr.com
5681
    name = 'caw4hw-tapa'
5682
    long_name = 'Caw4hw (from Tapastic)'
5683
    url = 'https://tapas.io/series/CAW4HW'
5684
5685
5686
class APleasantWasteOfTimeTapa(GenericTapasticComic):
5687
    """Class to retrieve A Pleasant Waste Of Time comics."""
5688
    # Also on https://artjcf.tumblr.com
5689
    name = 'pleasant-waste-tapa'
5690
    long_name = 'A Pleasant Waste Of Time (from Tapastic)'
5691
    url = 'https://tapas.io/series/A-Pleasant-'
5692
    _categories = ('WASTE', )
5693
5694
5695
class AbsurdoLapin(GenericNavigableComic):
5696
    """Class to retrieve Absurdo Lapin comics."""
5697
    name = 'absurdo'
5698
    long_name = 'Absurdo'
5699
    url = 'https://absurdo.lapin.org'
5700
    get_url_from_link = join_cls_url_to_href
5701
5702
    @classmethod
5703
    def get_nav(cls, soup):
5704
        """Get the navigation elements from soup object."""
5705
        cont = soup.find('div', id='content')
5706
        _, b2 = cont.find_all('div', class_='buttons')
5707
        # prev, first, last, next
5708
        return [li.find('a') for li in b2.find_all('li')]
5709
5710
    @classmethod
5711
    def get_first_comic_link(cls):
5712
        """Get link to first comics."""
5713
        return cls.get_nav(get_soup_at_url(cls.url))[1]
5714
5715
    @classmethod
5716
    def get_navi_link(cls, last_soup, next_):
5717
        """Get link to next or previous comic."""
5718
        return cls.get_nav(last_soup)[3 if next_ else 0]
5719
5720
    @classmethod
5721
    def get_comic_info(cls, soup, link):
5722
        """Get information about a particular comics."""
5723
        author = soup.find('meta', attrs={'name': 'author'})['content']
5724
        tags = soup.find('meta', attrs={'name': 'keywords'})['content']
5725
        title = soup.find('title').string
5726
        imgs = soup.find('div', id='content').find_all('img')
5727
        return {
5728
            'title': title,
5729
            'img': [urljoin_wrapper(cls.url, i['src']) for i in imgs],
5730
            'tags': tags,
5731
            'author': author,
5732
        }
5733
5734
5735
def get_subclasses(klass):
5736
    """Gets the list of direct/indirect subclasses of a class"""
5737
    subclasses = klass.__subclasses__()
5738
    for derived in list(subclasses):
5739
        subclasses.extend(get_subclasses(derived))
5740
    return subclasses
5741
5742
5743
def remove_st_nd_rd_th_from_date(string):
5744
    """Function to transform 1st/2nd/3rd/4th in a parsable date format."""
5745
    # Hackish way to convert string with numeral "1st"/"2nd"/etc to date
5746
    return (string.replace('st', '')
5747
            .replace('nd', '')
5748
            .replace('rd', '')
5749
            .replace('th', '')
5750
            .replace('Augu', 'August'))
5751
5752
5753
def string_to_date(string, date_format, local=DEFAULT_LOCAL):
5754
    """Function to convert string to date object.
5755
    Wrapper around datetime.datetime.strptime."""
5756
    # format described in https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior
5757
    prev_locale = locale.setlocale(locale.LC_ALL)
5758
    if local != prev_locale:
5759
        locale.setlocale(locale.LC_ALL, local)
5760
    ret = datetime.datetime.strptime(string, date_format).date()
5761
    if local != prev_locale:
5762
        locale.setlocale(locale.LC_ALL, prev_locale)
5763
    return ret
5764
5765
5766
COMICS = set(get_subclasses(GenericComic))
5767
VALID_COMICS = [c for c in COMICS if c.name is not None]
5768
COMIC_NAMES = {c.name: c for c in VALID_COMICS}
5769
assert len(VALID_COMICS) == len(COMIC_NAMES)
5770
CLASS_NAMES = {c.__name__ for c in VALID_COMICS}
5771
assert len(VALID_COMICS) == len(CLASS_NAMES)
5772