Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mikf
GitHub Repository: mikf/gallery-dl
Path: blob/master/gallery_dl/extractor/35photo.py
5399 views
1
# -*- coding: utf-8 -*-
2
3
# Copyright 2019-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://35photo.pro/"""
10
11
from .common import Extractor, Message
12
from .. import text
13
14
15
class _35photoExtractor(Extractor):
16
category = "35photo"
17
directory_fmt = ("{category}", "{user}")
18
filename_fmt = "{id}{title:?_//}_{num:>02}.{extension}"
19
archive_fmt = "{id}_{num}"
20
root = "https://35photo.pro"
21
22
def items(self):
23
first = True
24
data = self.metadata()
25
26
for photo_id in self.photos():
27
for photo in self._photo_data(photo_id):
28
photo.update(data)
29
url = photo["url"]
30
if first:
31
first = False
32
yield Message.Directory, photo
33
yield Message.Url, url, text.nameext_from_url(url, photo)
34
35
def metadata(self):
36
"""Returns general metadata"""
37
return {}
38
39
def photos(self):
40
"""Returns an iterable containing all relevant photo IDs"""
41
42
def _pagination(self, params, extra_ids=None):
43
url = "https://35photo.pro/show_block.php"
44
headers = {"Referer": self.root, "X-Requested-With": "XMLHttpRequest"}
45
params["type"] = "getNextPageData"
46
47
if "lastId" not in params:
48
params["lastId"] = "999999999"
49
if extra_ids:
50
yield from extra_ids
51
while params["lastId"]:
52
data = self.request_json(url, headers=headers, params=params)
53
yield from self._photo_ids(data["data"])
54
params["lastId"] = data["lastId"]
55
56
def _photo_data(self, photo_id):
57
params = {"method": "photo.getData", "photoId": photo_id}
58
data = self.request_json(
59
"https://api.35photo.pro/", params=params)["data"][photo_id]
60
info = {
61
"url" : data["src"],
62
"id" : data["photo_id"],
63
"title" : data["photo_name"],
64
"description": data["photo_desc"],
65
"tags" : data["tags"] or [],
66
"views" : data["photo_see"],
67
"favorites" : data["photo_fav"],
68
"score" : data["photo_rating"],
69
"type" : data["photo_type"],
70
"date" : data["timeAdd"],
71
"user" : data["user_login"],
72
"user_id" : data["user_id"],
73
"user_name" : data["user_name"],
74
}
75
76
if "series" in data:
77
for info["num"], photo in enumerate(data["series"], 1):
78
info["url"] = photo["src"]
79
info["id_series"] = text.parse_int(photo["id"])
80
info["title_series"] = photo["title"] or ""
81
yield info.copy()
82
else:
83
info["num"] = 1
84
yield info
85
86
def _photo_ids(self, page):
87
"""Extract unique photo IDs and return them as sorted list"""
88
# searching for photo-id="..." doesn't always work (see unit tests)
89
if not page:
90
return ()
91
return sorted(
92
set(text.extract_iter(page, "/photo_", "/")),
93
key=text.parse_int,
94
reverse=True,
95
)
96
97
98
class _35photoUserExtractor(_35photoExtractor):
99
"""Extractor for all images of a user on 35photo.pro"""
100
subcategory = "user"
101
pattern = (r"(?:https?://)?(?:[a-z]+\.)?35photo\.pro"
102
r"/(?!photo_|genre_|tags/|rating/)([^/?#]+)")
103
example = "https://35photo.pro/USER"
104
105
def __init__(self, match):
106
_35photoExtractor.__init__(self, match)
107
self.user = match[1]
108
self.user_id = 0
109
110
def metadata(self):
111
url = f"{self.root}/{self.user}/"
112
page = self.request(url).text
113
self.user_id = text.parse_int(text.extr(page, "/user_", ".xml"))
114
return {
115
"user": self.user,
116
"user_id": self.user_id,
117
}
118
119
def photos(self):
120
return self._pagination({
121
"page": "photoUser",
122
"user_id": self.user_id,
123
})
124
125
126
class _35photoTagExtractor(_35photoExtractor):
127
"""Extractor for all photos from a tag listing"""
128
subcategory = "tag"
129
directory_fmt = ("{category}", "Tags", "{search_tag}")
130
archive_fmt = "t{search_tag}_{id}_{num}"
131
pattern = r"(?:https?://)?(?:[a-z]+\.)?35photo\.pro/tags/([^/?#]+)"
132
example = "https://35photo.pro/tags/TAG/"
133
134
def __init__(self, match):
135
_35photoExtractor.__init__(self, match)
136
self.tag = match[1]
137
138
def metadata(self):
139
return {"search_tag": text.unquote(self.tag).lower()}
140
141
def photos(self):
142
num = 1
143
144
while True:
145
url = f"{self.root}/tags/{self.tag}/list_{num}/"
146
page = self.request(url).text
147
prev = None
148
149
for photo_id in text.extract_iter(page, "35photo.pro/photo_", "/"):
150
if photo_id != prev:
151
prev = photo_id
152
yield photo_id
153
154
if not prev:
155
return
156
num += 1
157
158
159
class _35photoGenreExtractor(_35photoExtractor):
160
"""Extractor for images of a specific genre on 35photo.pro"""
161
subcategory = "genre"
162
directory_fmt = ("{category}", "Genre", "{genre}")
163
archive_fmt = "g{genre_id}_{id}_{num}"
164
pattern = r"(?:https?://)?(?:[a-z]+\.)?35photo\.pro/genre_(\d+)(/new/)?"
165
example = "https://35photo.pro/genre_12345/"
166
167
def __init__(self, match):
168
_35photoExtractor.__init__(self, match)
169
self.genre_id, self.new = match.groups()
170
self.photo_ids = None
171
172
def metadata(self):
173
url = f"{self.root}/genre_{self.genre_id}{self.new or '/'}"
174
page = self.request(url).text
175
self.photo_ids = self._photo_ids(text.extr(
176
page, ' class="photo', '\n'))
177
return {
178
"genre": text.extr(page, " genre - ", ". "),
179
"genre_id": text.parse_int(self.genre_id),
180
}
181
182
def photos(self):
183
if not self.photo_ids:
184
return ()
185
return self._pagination({
186
"page": "genre",
187
"community_id": self.genre_id,
188
"photo_rating": "0" if self.new else "50",
189
"lastId": self.photo_ids[-1],
190
}, self.photo_ids)
191
192
193
class _35photoImageExtractor(_35photoExtractor):
194
"""Extractor for individual images from 35photo.pro"""
195
subcategory = "image"
196
pattern = r"(?:https?://)?(?:[a-z]+\.)?35photo\.pro/photo_(\d+)"
197
example = "https://35photo.pro/photo_12345/"
198
199
def __init__(self, match):
200
_35photoExtractor.__init__(self, match)
201
self.photo_id = match[1]
202
203
def photos(self):
204
return (self.photo_id,)
205
206