Completed
Push — master ( 33aa40...8290e3 )
by De
36s
created

comics.py (43 issues)

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