Completed
Push — master ( c929cd...be0f27 )
by De
28s
created

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