Completed
Push — master ( 1bb579...51f5c7 )
by De
01:04
created

comics.py (18 issues)

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