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

comics.py (28 issues)

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