Completed
Push — master ( 07fd0e...1f662b )
by De
28s
created

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