Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mikf
GitHub Repository: mikf/gallery-dl
Path: blob/master/gallery_dl/extractor/aryion.py
5399 views
1
# -*- coding: utf-8 -*-
2
3
# Copyright 2020-2025 Mike Fährmann
4
#
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License version 2 as
7
# published by the Free Software Foundation.
8
9
"""Extractors for https://aryion.com/"""
10
11
from .common import Extractor, Message
12
from .. import text, util, exception
13
from ..cache import cache
14
from email.utils import parsedate_tz
15
from datetime import datetime
16
17
BASE_PATTERN = r"(?:https?://)?(?:www\.)?aryion\.com/g4"
18
19
20
class AryionExtractor(Extractor):
21
"""Base class for aryion extractors"""
22
category = "aryion"
23
directory_fmt = ("{category}", "{user!l}", "{path:J - }")
24
filename_fmt = "{id} {title}.{extension}"
25
archive_fmt = "{id}"
26
cookies_domain = ".aryion.com"
27
cookies_names = ("phpbb3_rl7a3_sid",)
28
root = "https://aryion.com"
29
30
def __init__(self, match):
31
Extractor.__init__(self, match)
32
self.user = match[1]
33
self.recursive = True
34
35
def login(self):
36
if self.cookies_check(self.cookies_names):
37
return
38
39
username, password = self._get_auth_info()
40
if username:
41
self.cookies_update(self._login_impl(username, password))
42
43
@cache(maxage=14*86400, keyarg=1)
44
def _login_impl(self, username, password):
45
self.log.info("Logging in as %s", username)
46
47
url = self.root + "/forum/ucp.php?mode=login"
48
data = {
49
"username": username,
50
"password": password,
51
"login": "Login",
52
}
53
54
response = self.request(url, method="POST", data=data)
55
if b"You have been successfully logged in." not in response.content:
56
raise exception.AuthenticationError()
57
return {c: response.cookies[c] for c in self.cookies_names}
58
59
def items(self):
60
self.login()
61
data = self.metadata()
62
63
for post_id in self.posts():
64
if post := self._parse_post(post_id):
65
if data:
66
post.update(data)
67
yield Message.Directory, post
68
yield Message.Url, post["url"], post
69
elif post is False and self.recursive:
70
base = self.root + "/g4/view/"
71
data = {"_extractor": AryionPostExtractor}
72
for post_id in self._pagination_params(base + post_id):
73
yield Message.Queue, base + post_id, data
74
75
def posts(self):
76
"""Yield relevant post IDs"""
77
78
def metadata(self):
79
"""Return general metadata"""
80
81
def _pagination_params(self, url, params=None, needle=None):
82
if params is None:
83
params = {"p": 1}
84
else:
85
params["p"] = text.parse_int(params.get("p"), 1)
86
87
if needle is None:
88
needle = "class='gallery-item' id='"
89
90
while True:
91
page = self.request(url, params=params).text
92
93
cnt = 0
94
for post_id in text.extract_iter(page, needle, "'"):
95
cnt += 1
96
yield post_id
97
98
if cnt < 40 and ">Next &gt;&gt;<" not in page:
99
return
100
params["p"] += 1
101
102
def _pagination_next(self, url):
103
while True:
104
page = self.request(url).text
105
yield from text.extract_iter(page, "thumb' href='/g4/view/", "'")
106
107
pos = page.find("Next &gt;&gt;")
108
if pos < 0:
109
return
110
url = self.root + text.rextr(page, "href='", "'", pos)
111
112
def _parse_post(self, post_id):
113
url = f"{self.root}/g4/data.php?id={post_id}"
114
with self.request(url, method="HEAD", fatal=False) as response:
115
116
if response.status_code >= 400:
117
self.log.warning(
118
"Unable to fetch post %s ('%s %s')",
119
post_id, response.status_code, response.reason)
120
return None
121
headers = response.headers
122
123
# folder
124
if headers["content-type"] in (
125
"application/x-folder",
126
"application/x-comic-folder",
127
"application/x-comic-folder-nomerge",
128
):
129
return False
130
131
# get filename from 'Content-Disposition' header
132
cdis = headers["content-disposition"]
133
fname, _, ext = text.extr(cdis, 'filename="', '"').rpartition(".")
134
if not fname:
135
fname, ext = ext, fname
136
137
# get file size from 'Content-Length' header
138
clen = headers.get("content-length")
139
140
# fix 'Last-Modified' header
141
lmod = headers["last-modified"]
142
if lmod[22] != ":":
143
lmod = f"{lmod[:22]}:{lmod[22:24]} GMT"
144
145
post_url = f"{self.root}/g4/view/{post_id}"
146
extr = text.extract_from(self.request(post_url).text)
147
148
title, _, artist = text.unescape(extr(
149
"<title>g4 :: ", "<")).rpartition(" by ")
150
151
return {
152
"id" : text.parse_int(post_id),
153
"url" : url,
154
"user" : self.user or artist,
155
"title" : title,
156
"artist": artist,
157
"path" : text.split_html(extr(
158
"cookiecrumb'>", '</span'))[4:-1:2],
159
"date" : datetime(*parsedate_tz(lmod)[:6]),
160
"size" : text.parse_int(clen),
161
"views" : text.parse_int(extr("Views</b>:", "<").replace(",", "")),
162
"width" : text.parse_int(extr("Resolution</b>:", "x")),
163
"height": text.parse_int(extr("", "<")),
164
"comments" : text.parse_int(extr("Comments</b>:", "<")),
165
"favorites": text.parse_int(extr("Favorites</b>:", "<")),
166
"tags" : text.split_html(extr("class='taglist'>", "</span>")),
167
"description": text.unescape(text.remove_html(extr(
168
"<p>", "</p>"), "", "")),
169
"filename" : fname,
170
"extension": ext,
171
"_http_lastmodified": lmod,
172
}
173
174
175
class AryionGalleryExtractor(AryionExtractor):
176
"""Extractor for a user's gallery on eka's portal"""
177
subcategory = "gallery"
178
categorytransfer = True
179
pattern = BASE_PATTERN + r"/(?:gallery/|user/|latest.php\?name=)([^/?#]+)"
180
example = "https://aryion.com/g4/gallery/USER"
181
182
def __init__(self, match):
183
AryionExtractor.__init__(self, match)
184
self.offset = 0
185
186
def _init(self):
187
self.recursive = self.config("recursive", True)
188
189
def skip(self, num):
190
if self.recursive:
191
return 0
192
self.offset += num
193
return num
194
195
def posts(self):
196
if self.recursive:
197
url = f"{self.root}/g4/gallery/{self.user}"
198
return self._pagination_params(url)
199
else:
200
url = f"{self.root}/g4/latest.php?name={self.user}"
201
return util.advance(self._pagination_next(url), self.offset)
202
203
204
class AryionFavoriteExtractor(AryionExtractor):
205
"""Extractor for a user's favorites gallery"""
206
subcategory = "favorite"
207
directory_fmt = ("{category}", "{user!l}", "favorites")
208
archive_fmt = "f_{user}_{id}"
209
categorytransfer = True
210
pattern = BASE_PATTERN + r"/favorites/([^/?#]+)"
211
example = "https://aryion.com/g4/favorites/USER"
212
213
def posts(self):
214
url = f"{self.root}/g4/favorites/{self.user}"
215
return self._pagination_params(url, None, "data-item-id='")
216
217
218
class AryionTagExtractor(AryionExtractor):
219
"""Extractor for tag searches on eka's portal"""
220
subcategory = "tag"
221
directory_fmt = ("{category}", "tags", "{search_tags}")
222
archive_fmt = "t_{search_tags}_{id}"
223
pattern = BASE_PATTERN + r"/tags\.php\?([^#]+)"
224
example = "https://aryion.com/g4/tags.php?tag=TAG"
225
226
def _init(self):
227
self.params = text.parse_query(self.user)
228
self.user = None
229
230
def metadata(self):
231
return {"search_tags": self.params.get("tag")}
232
233
def posts(self):
234
url = self.root + "/g4/tags.php"
235
return self._pagination_params(url, self.params)
236
237
238
class AryionPostExtractor(AryionExtractor):
239
"""Extractor for individual posts on eka's portal"""
240
subcategory = "post"
241
pattern = BASE_PATTERN + r"/view/(\d+)"
242
example = "https://aryion.com/g4/view/12345"
243
244
def posts(self):
245
post_id, self.user = self.user, None
246
return (post_id,)
247
248