local: Treat dots/periods as digits in destructuring directories.
[automanga.git] / manga / mangafox.py
1 import urllib.request, urllib.parse, re
2 import bs4, json
3 from . import lib, htcache
4 soup = bs4.BeautifulSoup
5 soupify = lambda cont: soup(cont, "html.parser")
6
7 class page(lib.page):
8     def __init__(self, chapter, stack, n, url):
9         self.stack = stack
10         self.chapter = chapter
11         self.volume = self.chapter.volume
12         self.manga = self.volume.manga
13         self.n = n
14         self.id = str(n)
15         self.name = "Page %s" % n
16         self.url = url
17         self.ciurl = None
18
19     def iurl(self):
20         if self.ciurl is None:
21             page = soupify(htcache.fetch(self.url))
22             self.ciurl = page.find("div", id="viewer").find("img", id="image")["src"]
23         return self.ciurl
24
25     def open(self):
26         return lib.stdimgstream(self.iurl())
27
28     def __str__(self):
29         return self.name
30
31     def __repr__(self):
32         return "<mangafox.page %r.%r.%r.%r>" % (self.manga.name, self.volume.name, self.chapter.name, self.name)
33
34 class chapter(lib.pagelist):
35     def __init__(self, volume, stack, id, name, url):
36         self.stack = stack
37         self.volume = volume
38         self.manga = volume.manga
39         self.id = id
40         self.name = name
41         self.url = url
42         self.cpag = None
43
44     def __getitem__(self, i):
45         return self.pages()[i]
46
47     def __len__(self):
48         return len(self.pages())
49
50     def pages(self):
51         if self.cpag is None:
52             pg = soupify(htcache.fetch(self.url + "1.html"))
53             l = pg.find("form", id="top_bar").find("div", attrs={"class": "l"})
54             if len(l.contents) != 3:
55                 raise Exception("parse error: weird page list for %r" % self)
56             m = l.contents[2].strip()
57             if m[:3] != "of ":
58                 raise Exception("parse error: weird page list for %r" % self)
59             self.cpag = [page(self, self.stack + [(self, n)], n + 1, self.url + ("%i.html" % (n + 1))) for n in range(int(m[3:]))]
60         return self.cpag
61
62     def __str__(self):
63         return self.name
64
65     def __repr__(self):
66         return "<mangafox.chapter %r.%r.%r>" % (self.manga.name, self.volume.name, self.name)
67
68 class volume(lib.pagelist):
69     def __init__(self, manga, stack, id, name):
70         self.stack = stack
71         self.manga = manga
72         self.id = id
73         self.name = name
74         self.ch = []
75
76     def __getitem__(self, i):
77         return self.ch[i]
78
79     def __len__(self):
80         return len(self.ch)
81
82     def __str__(self):
83         return self.name
84
85     def __repr__(self):
86         return "<mangafox.volume %r.%r>" % (self.manga.name, self.name)
87
88 def nextel(el):
89     while True:
90         el = el.nextSibling
91         if isinstance(el, bs4.Tag):
92             return el
93
94 class manga(lib.manga):
95     cure = re.compile(r"/c[\d.]+/$")
96     
97     def __init__(self, lib, id, name, url):
98         self.lib = lib
99         self.id = id
100         self.name = name
101         self.url = url
102         self.cvol = None
103         self.stack = []
104
105     def __getitem__(self, i):
106         return self.vols()[i]
107
108     def __len__(self):
109         return len(self.vols())
110
111     def vols(self):
112         if self.cvol is None:
113             page = soupify(htcache.fetch(self.url))
114             vls = page.find("div", id="chapters").findAll("div", attrs={"class": "slide"})
115             cvol = []
116             for i, vn in enumerate(reversed(vls)):
117                 name = vn.find("h3", attrs={"class": "volume"}).contents[0].strip()
118                 vol = volume(self, [(self, i)], name, name)
119                 cls = nextel(vn)
120                 if cls.name != "ul" or "chlist" not in cls["class"]:
121                     raise Exception("parse error: weird volume list for %r" % self)
122                 for o, ch in enumerate(reversed(cls.findAll("li"))):
123                     n = ch.div.h3 or ch.div.h4
124                     chid = name = n.a.string
125                     for span in ch("span"):
126                         try:
127                             if "title" in span["class"]:
128                                 name += " " + span.string
129                         except KeyError:
130                             pass
131                     url = urllib.parse.urljoin(self.url, n.a["href"])
132                     if url[-7:] == "/1.html":
133                         url = url[:-6]
134                     elif self.cure.search(url) is not None:
135                         pass
136                     else:
137                         raise Exception("parse error: unexpected chapter URL for %r: %s" % (self, url))
138                     vol.ch.append(chapter(vol, vol.stack + [(vol, o)], chid, name, url))
139                 cvol.append(vol)
140             self.cvol = cvol
141         return self.cvol
142
143     def __str__(self):
144         return self.name
145
146     def __repr__(self):
147         return "<mangafox.manga %r>" % self.name
148
149 def libalphacmp(a, b):
150     if a.upper() < b.upper():
151         return -1
152     elif a.upper() > b.upper():
153         return 1
154     return 0
155
156 class library(lib.library):
157     def __init__(self):
158         self.base = "http://mangafox.me/"
159
160     def alphapage(self, pno):
161         abase = self.base + ("directory/%i.htm?az" % pno)
162         page = soupify(htcache.fetch(abase))
163         ls = page.find("div", id="mangalist").find("ul", attrs={"class": "list"}).findAll("li")
164         ret = []
165         for m in ls:
166             t = m.find("div", attrs={"class": "manga_text"}).find("a", attrs={"class": "title"})
167             name = t.string
168             url = urllib.parse.urljoin(abase, t["href"])
169             p = url.find("/manga/")
170             if p < 0 or url.find('/', p + 7) != (len(url) - 1):
171                 raise Exception("parse error: unexpected manga URL for %r: %s" % (name, url))
172             ret.append(manga(self, url[p + 7:-1], name, url))
173         return ret
174
175     def alphapages(self):
176         page = soupify(htcache.fetch(self.base + "directory/?az"))
177         ls = page.find("div", id="mangalist").find("div", id="nav").find("ul").findAll("li")
178         return int(ls[-2].find("a").string)
179
180     def byname(self, prefix):
181         l = 1
182         r = self.alphapages()
183         while True:
184             if l > r:
185                 return
186             c = l + ((r + 1 - l) // 2)
187             ls = self.alphapage(c)
188             if libalphacmp(ls[0].name, prefix) > 0:
189                 r = c - 1
190             elif libalphacmp(ls[-1].name, prefix) < 0:
191                 l = c + 1
192             else:
193                 pno = c
194                 break
195         i = 0
196         while i < len(ls):
197             m = ls[i]
198             if libalphacmp(m.name, prefix) >= 0:
199                 break
200             i += 1
201         while True:
202             while i < len(ls):
203                 m = ls[i]
204                 if not m.name[:len(prefix)].upper() == prefix.upper():
205                     return
206                 yield m
207                 i += 1
208             pno += 1
209             ls = self.alphapage(pno)
210             i = 0
211
212     def search(self, expr):
213         req = urllib.request.Request(self.base + ("ajax/search.php?term=%s" % urllib.parse.quote(expr)),
214                                      headers={"User-Agent": "automanga/1"})
215         with urllib.request.urlopen(req) as resp:
216             rc = json.loads(resp.read().decode("utf-8"))
217         return [manga(self, id, name, self.base + ("manga/%s/" % id)) for num, name, id, genres, author in rc]
218
219     def byid(self, id):
220         url = self.base + ("manga/%s/" % id)
221         page = soupify(htcache.fetch(url))
222         if page.find("div", id="title") is None:
223             # Assume we got the search page
224             raise KeyError(id)
225         name = page.find("div", id="series_info").find("div", attrs={"class": "cover"}).img["alt"]
226         return manga(self, id, name, url)
227
228     def __iter__(self):
229         raise NotImplementedError("mangafox iterator")