Completed
Push — master ( 0d51a1...37c82c )
by De
34s
created

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