structables 0.3.12__tar.gz → 0.3.13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {structables-0.3.12 → structables-0.3.13}/PKG-INFO +1 -1
  2. {structables-0.3.12 → structables-0.3.13}/pyproject.toml +1 -1
  3. structables-0.3.13/src/structables/routes/contest.py +174 -0
  4. {structables-0.3.12 → structables-0.3.13}/src/structables/static/css/style.css +48 -120
  5. structables-0.3.13/src/structables/templates/archives.html +38 -0
  6. structables-0.3.13/src/structables/templates/contest.html +23 -0
  7. structables-0.3.13/src/structables/templates/contests.html +21 -0
  8. structables-0.3.12/src/structables/routes/contest.py +0 -177
  9. structables-0.3.12/src/structables/templates/archives.html +0 -31
  10. structables-0.3.12/src/structables/templates/contest.html +0 -23
  11. structables-0.3.12/src/structables/templates/contests.html +0 -40
  12. {structables-0.3.12 → structables-0.3.13}/.env.example +0 -0
  13. {structables-0.3.12 → structables-0.3.13}/.forgejo/workflows/docker.yml +0 -0
  14. {structables-0.3.12 → structables-0.3.13}/.forgejo/workflows/release.yml +0 -0
  15. {structables-0.3.12 → structables-0.3.13}/.gitignore +0 -0
  16. {structables-0.3.12 → structables-0.3.13}/Dockerfile +0 -0
  17. {structables-0.3.12 → structables-0.3.13}/LICENSE +0 -0
  18. {structables-0.3.12 → structables-0.3.13}/README.md +0 -0
  19. {structables-0.3.12 → structables-0.3.13}/cronjob.txt +0 -0
  20. {structables-0.3.12 → structables-0.3.13}/docker-compose-example-dev.yml +0 -0
  21. {structables-0.3.12 → structables-0.3.13}/docker-compose-example.yml +0 -0
  22. {structables-0.3.12 → structables-0.3.13}/entrypoint.sh +0 -0
  23. {structables-0.3.12 → structables-0.3.13}/src/structables/__init__.py +0 -0
  24. {structables-0.3.12 → structables-0.3.13}/src/structables/config.py +0 -0
  25. {structables-0.3.12 → structables-0.3.13}/src/structables/main.py +0 -0
  26. {structables-0.3.12 → structables-0.3.13}/src/structables/routes/__init__.py +0 -0
  27. {structables-0.3.12 → structables-0.3.13}/src/structables/routes/category.py +0 -0
  28. {structables-0.3.12 → structables-0.3.13}/src/structables/routes/main.py +0 -0
  29. {structables-0.3.12 → structables-0.3.13}/src/structables/routes/member.py +0 -0
  30. {structables-0.3.12 → structables-0.3.13}/src/structables/routes/proxy.py +0 -0
  31. {structables-0.3.12 → structables-0.3.13}/src/structables/static/dist/css/bootstrap.min.css +0 -0
  32. {structables-0.3.12 → structables-0.3.13}/src/structables/static/dist/css/bootstrap.min.css.map +0 -0
  33. {structables-0.3.12 → structables-0.3.13}/src/structables/static/img/logo.png +0 -0
  34. {structables-0.3.12 → structables-0.3.13}/src/structables/static/img/logo_lg.png +0 -0
  35. {structables-0.3.12 → structables-0.3.13}/src/structables/static/img/magnifying-glass-solid.svg +0 -0
  36. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/400.html +0 -0
  37. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/404.html +0 -0
  38. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/429.html +0 -0
  39. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/500.html +0 -0
  40. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/article-review.html +0 -0
  41. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/article.html +0 -0
  42. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/base.html +0 -0
  43. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/category.html +0 -0
  44. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/collection.html +0 -0
  45. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/footer.html +0 -0
  46. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/header.html +0 -0
  47. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/iframe.html +0 -0
  48. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/index.html +0 -0
  49. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/member-instructables.html +0 -0
  50. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/member.html +0 -0
  51. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/privacypolicy.html +0 -0
  52. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/projects.html +0 -0
  53. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/sitemap.html +0 -0
  54. {structables-0.3.12 → structables-0.3.13}/src/structables/templates/style.html +0 -0
  55. {structables-0.3.12 → structables-0.3.13}/src/structables/utils/data.py +0 -0
  56. {structables-0.3.12 → structables-0.3.13}/src/structables/utils/helpers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: structables
3
- Version: 0.3.12
3
+ Version: 0.3.13
4
4
  Summary: A simple frontend for Instructables
5
5
  Project-URL: Homepage, https://git.private.coffee/privatecoffee/structables
6
6
  Project-URL: Bug Tracker, https://git.private.coffee/privatecoffee/structables/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "structables"
7
- version = "0.3.12"
7
+ version = "0.3.13"
8
8
  authors = [
9
9
  { name="Private.coffee Team", email="support@private.coffee" },
10
10
  ]
@@ -0,0 +1,174 @@
1
+ from flask import render_template, request, abort, url_for
2
+ from urllib.request import urlopen, Request
3
+ from urllib.error import HTTPError
4
+ from ..utils.helpers import proxy
5
+ from bs4 import BeautifulSoup
6
+ import json
7
+
8
+
9
+ def init_contest_routes(app):
10
+ @app.route("/contest/archive/")
11
+ def route_contest_archive():
12
+ # Default pagination settings
13
+ limit = 10
14
+ page = request.args.get("page", default=1, type=int)
15
+ offset = (page - 1) * limit
16
+
17
+ try:
18
+ # Fetch data using urlopen
19
+ url = f"https://www.instructables.com/json-api/getClosedContests?limit={limit}&offset={offset}"
20
+ response = urlopen(url)
21
+ data = json.loads(response.read().decode())
22
+ except HTTPError as e:
23
+ abort(e.code)
24
+ except Exception as e:
25
+ abort(500) # Handle other exceptions like JSON decode errors
26
+
27
+ contests = data.get("contests", [])
28
+ full_list_size = data.get("fullListSize", 0)
29
+
30
+ contest_list = []
31
+ for contest in contests:
32
+ contest_details = {
33
+ "title": contest["title"],
34
+ "link": url_for("route_contest", contest=contest["urlString"]),
35
+ "deadline": contest["deadline"],
36
+ "startDate": contest["startDate"],
37
+ "numEntries": contest["numEntries"],
38
+ "state": contest["state"],
39
+ "bannerUrl": proxy(contest["bannerUrlMedium"]),
40
+ }
41
+ contest_list.append(contest_details)
42
+
43
+ # Calculate total pages
44
+ total_pages = (full_list_size + limit - 1) // limit
45
+
46
+ # Create pagination
47
+ pagination = {
48
+ "current_page": page,
49
+ "total_pages": total_pages,
50
+ "has_prev": page > 1,
51
+ "has_next": page < total_pages,
52
+ "limit": limit,
53
+ }
54
+
55
+ return render_template(
56
+ "archives.html",
57
+ title=f"Contest Archives (Page {page})",
58
+ page=page,
59
+ pagination=pagination,
60
+ contest_list=contest_list,
61
+ )
62
+
63
+ def get_entries(contest):
64
+ base_url = f"https://www.instructables.com/api_proxy/search/collections/projects/documents/search"
65
+ headers = {"x-typesense-api-key": app.typesense_api_key}
66
+ page, per_page = 1, 100
67
+ all_entries = []
68
+
69
+ while True:
70
+ try:
71
+ url = f"{base_url}?q=*&filter_by=contestPath:{contest}&sort_by=contestEntryDate:desc&per_page={per_page}&page={page}"
72
+ request = Request(url, headers=headers)
73
+ response = urlopen(request)
74
+ data = json.loads(response.read().decode())
75
+ except HTTPError as e:
76
+ abort(e.code)
77
+
78
+ hits = data.get("hits", [])
79
+ if not hits:
80
+ break
81
+
82
+ all_entries.extend(hits)
83
+ if len(hits) < per_page:
84
+ break
85
+ page += 1
86
+
87
+ return all_entries
88
+
89
+ @app.route("/contest/<contest>/")
90
+ def route_contest(contest):
91
+ try:
92
+ data = urlopen(f"https://www.instructables.com/contest/{contest}/")
93
+ html = data.read().decode()
94
+ soup = BeautifulSoup(html, "html.parser")
95
+
96
+ title_tag = soup.find("h1")
97
+ title = title_tag.get_text() if title_tag else "Contest"
98
+
99
+ img_tag = soup.find("img", alt=lambda x: x and "Banner" in x)
100
+ img = img_tag.get("src") if img_tag else "default.jpg"
101
+
102
+ entry_count = len(get_entries(contest))
103
+ prizes_items = soup.select("article")
104
+ prizes = len(prizes_items) if prizes_items else 0
105
+
106
+ overview_section = soup.find("section", id="overview")
107
+ info = (
108
+ overview_section.decode_contents()
109
+ if overview_section
110
+ else "No Overview"
111
+ )
112
+
113
+ except HTTPError as e:
114
+ abort(e.code)
115
+
116
+ entry_list = []
117
+ entries = get_entries(contest)
118
+ for entry in entries:
119
+ doc = entry["document"]
120
+ entry_details = {
121
+ "link": url_for("route_article", article=doc["urlString"]),
122
+ "entry_img": doc["coverImageUrl"],
123
+ "entry_title": doc["title"],
124
+ "author": doc["screenName"],
125
+ "author_link": url_for("route_member", member=doc["screenName"]),
126
+ "channel": doc["channel"][0],
127
+ "channel_link": f"/{doc['primaryClassification']}",
128
+ "views": doc.get("views", 0),
129
+ }
130
+ entry_list.append(entry_details)
131
+
132
+ return render_template(
133
+ "contest.html",
134
+ title=title,
135
+ img=img,
136
+ entry_count=entry_count,
137
+ prizes=prizes,
138
+ info=info,
139
+ entry_list=entry_list,
140
+ )
141
+
142
+ @app.route("/contest/")
143
+ def route_contests():
144
+ try:
145
+ # Fetch current contests from the JSON API
146
+ response = urlopen(
147
+ "https://www.instructables.com/json-api/getCurrentContests?limit=50&offset=0"
148
+ )
149
+ data = json.loads(response.read().decode())
150
+ except HTTPError as e:
151
+ abort(e.code)
152
+ except Exception as e:
153
+ abort(500) # Handle other exceptions such as JSON decode errors
154
+
155
+ contests = data.get("contests", [])
156
+ contest_list = []
157
+ for contest in contests:
158
+ contest_details = {
159
+ "link": url_for("route_contest", contest=contest["urlString"]),
160
+ "img": proxy(contest["bannerUrlMedium"]),
161
+ "alt": contest["title"],
162
+ "title": contest["title"],
163
+ "deadline": contest["deadline"],
164
+ "prizes": contest["prizeCount"],
165
+ "entries": contest["numEntries"],
166
+ }
167
+ contest_list.append(contest_details)
168
+
169
+ return render_template(
170
+ "contests.html",
171
+ title="Contests",
172
+ contest_count=len(contest_list),
173
+ contests=contest_list,
174
+ )
@@ -130,144 +130,67 @@ blockquote {
130
130
  }
131
131
  }
132
132
 
133
- .ibles {
134
- display: inline-block;
135
- vertical-align: top;
136
- }
137
-
138
- .ible-small {
139
- font-size: 0.7em;
140
- font-weight: thin;
141
- line-height: 1em;
142
- }
143
-
144
- .step-section {
145
- background-color: var(--primary-bg);
146
- border: 1px solid var(--border-color);
147
- border-radius: 8px;
148
- box-shadow: 0 2px 4px var(--shadow-color);
149
- padding: 1.5rem;
150
- }
151
-
152
- .step-header h2 {
153
- font-size: 2em;
154
- color: var(--heading-color);
155
- }
156
-
157
- .step-images img,
158
- .step-videos video,
159
- .step-iframes iframe {
160
- border-radius: 8px;
161
- box-shadow: 0 2px 4px var(--shadow-color);
162
- }
163
-
164
- .step-text {
165
- font-size: 1.1em;
166
- }
167
-
168
- .reply-button,
169
- .replies {
170
- display: none;
171
- }
172
-
173
- .reply-button+label {
174
- position: relative;
175
- display: block;
176
- cursor: pointer;
177
- }
178
-
179
- input.reply-button:checked+label+.replies {
133
+ .contest-list {
180
134
  display: flex;
181
135
  flex-direction: column;
182
- gap: 1rem;
183
- margin-top: 1rem;
136
+ gap: 20px;
184
137
  }
185
138
 
186
- .member-list {
187
- display: inline-block;
188
- max-width: 200px;
189
- vertical-align: top;
190
- }
191
-
192
- .ible-list-item {
193
- display: inline-block;
194
- max-width: 350px;
195
- vertical-align: top;
196
- margin-bottom: 2rem;
197
- }
198
-
199
- .contest-list-item {
200
- display: inline-block;
201
- max-width: 500px;
202
- vertical-align: top;
203
- margin-bottom: 2rem;
139
+ .contest-item {
140
+ background-color: #fff;
141
+ padding: 20px;
142
+ border: 1px solid var(--border-color);
143
+ border-radius: 8px;
144
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
204
145
  }
205
146
 
206
- .archive-month-wrapper {
207
- display: inline-block;
208
- width: 30vw;
209
- vertical-align: top;
147
+ .contest-item img {
148
+ max-width: 100%;
149
+ height: auto;
150
+ border-radius: 4px;
151
+ margin-top: 10px;
210
152
  }
211
153
 
212
- .archive-month {
154
+ .pagination {
213
155
  display: flex;
214
- flex-direction: column;
215
- gap: -10px;
216
- margin-bottom: 1rem;
217
- justify-content: space-between;
218
- }
219
-
220
- .archive {
221
- margin-bottom: -20px;
156
+ justify-content: center;
157
+ padding: 10px 0;
158
+ list-style-type: none;
222
159
  }
223
160
 
224
- ul.pagination {
161
+ .pagination-list {
225
162
  display: flex;
226
- justify-content: space-around;
227
- padding: 0 33vw;
228
- list-style-type: none;
229
163
  align-items: center;
164
+ gap: 10px;
230
165
  }
231
166
 
232
- ul.pagination li.active a,
233
- ul.pagination li.disabled a,
234
- ul.pagination li.active a:hover,
235
- ul.pagination li.disabled a:hover {
236
- color: #bbc2cf;
237
- text-decoration: none;
167
+ .pagination-list li {
168
+ display: inline-block;
238
169
  }
239
170
 
240
- .closed-contest-contest {
241
- object-fit: cover;
242
- width: 33vw;
243
- height: 15vw;
244
- display: inline-block;
245
- vertical-align: top;
246
- padding: 0 10px;
171
+ .pagination-list a {
172
+ color: var(--main-color);
173
+ text-decoration: none;
174
+ padding: 5px 10px;
175
+ border-radius: 4px;
176
+ border: 1px solid transparent;
177
+ transition: all 0.3s ease;
247
178
  }
248
179
 
249
- .closed-contest-winner,
250
- .closed-contest-winner-img {
251
- width: 15vw;
252
- display: inline-block;
253
- vertical-align: top;
254
- padding: 0 10px;
255
- font-size: 0.8em;
256
- text-overflow: ellipsis;
257
- white-space: nowrap;
258
- overflow: hidden;
180
+ .pagination-list a:hover {
181
+ border-color: var(--link-color);
182
+ color: var(--link-color);
259
183
  }
260
184
 
261
- .sitemap-group {
262
- margin-top: 2em;
263
- display: inline-block;
264
- width: 30vw;
265
- text-align: left;
266
- vertical-align: top;
185
+ .pagination-list li.active a {
186
+ background-color: var(--link-color);
187
+ color: #fff;
188
+ border-color: var(--link-color);
267
189
  }
268
190
 
269
- .sitemap-group h2 {
270
- text-align: center;
191
+ .pagination-list li.disabled a {
192
+ color: #ccc;
193
+ cursor: not-allowed;
271
194
  }
272
195
 
273
196
  .container {
@@ -280,7 +203,6 @@ header {
280
203
 
281
204
  .go_here_link {
282
205
  background-color: #4caf50;
283
- /* Green */
284
206
  border: none;
285
207
  color: white;
286
208
  padding: 15px 32px;
@@ -299,10 +221,6 @@ header {
299
221
  overflow-wrap: break-word;
300
222
  }
301
223
 
302
- .navbar-logo {
303
- height: 64px;
304
- }
305
-
306
224
  .img-fluid {
307
225
  border-radius: 8px;
308
226
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
@@ -322,7 +240,17 @@ iframe {
322
240
  color: #6c757d !important;
323
241
  }
324
242
 
243
+ .sitemap-group {
244
+ margin-top: 2em;
245
+ display: inline-block;
246
+ width: 30vw;
247
+ text-align: left;
248
+ vertical-align: top;
249
+ }
325
250
 
251
+ .sitemap-group h2 {
252
+ text-align: center;
253
+ }
326
254
 
327
255
  .sitemap-group .card {
328
256
  background-color: var(--primary-bg);
@@ -0,0 +1,38 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <center>
5
+ <h1>Past Contests</h1>
6
+ <p>Total Contests: {{ pagination.total_pages * pagination.limit }}</p>
7
+ <p><a href="/contest/">See running contests</a></p>
8
+ </center>
9
+ <div class="contest-list">
10
+ {% for contest in contest_list %}
11
+ <div class="contest-item">
12
+ <hr>
13
+ <h2>{{ contest.title }}</h2>
14
+ <p>
15
+ <a href="{{ contest.link }}">{{ contest.title }}</a><br>
16
+ Start Date: {{ contest.startDate }}<br>
17
+ Deadline: {{ contest.deadline }}<br>
18
+ Entries: {{ contest.numEntries }}<br>
19
+ Status: {{ contest.state }}<br>
20
+ </p>
21
+ <img src="{{ contest.bannerUrl }}" alt="{{ contest.title }} banner" style="max-width:100%;">
22
+ <hr>
23
+ </div>
24
+ {% endfor %}
25
+ </div>
26
+ <hr>
27
+ <div class="pagination">
28
+ <ul class="pagination-list">
29
+ {% if pagination.has_prev %}
30
+ <li><a href="?page={{ pagination.current_page - 1 }}">Previous</a></li>
31
+ {% endif %}
32
+ <li>Page {{ pagination.current_page }} of {{ pagination.total_pages }}</li>
33
+ {% if pagination.has_next %}
34
+ <li><a href="?page={{ pagination.current_page + 1 }}">Next</a></li>
35
+ {% endif %}
36
+ </ul>
37
+ </div>
38
+ {% endblock %}
@@ -0,0 +1,23 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <center>
5
+ <h1>{{ title }}</h1>
6
+ <img src="{{ img }}" alt="{{ title }}" style="max-width:98vw;">
7
+ <p>{{ entry_count }} Entries, {{ prizes }} Prizes</p>
8
+ <br>
9
+ {{ info|safe }}
10
+ <div class="ible-list">
11
+ {% for ible in entry_list %}
12
+ <div class="ible-list-item">
13
+ <a href="{{ ible.link }}" style="color:#bbc2cf;">
14
+ <img style="max-width:350px;" src="{{ ible.entry_img }}" alt="{{ ible.entry_title }}">
15
+ <p>{{ ible.entry_title }}</p>
16
+ </a>
17
+ <p>by <a href="{{ ible.author_link }}">{{ ible.author }}</a> in <a href="{{ ible.channel_link }}">{{ ible.channel }}</a></p>
18
+ <p>{{ ible.views }} Views</p>
19
+ </div>
20
+ {% endfor %}
21
+ </div>
22
+ </center>
23
+ {% endblock %}
@@ -0,0 +1,21 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <center>
5
+ <h1>{{ title }}</h1>
6
+ <p>Total Running Contests: {{ contest_count }}</p>
7
+ <br>
8
+ <div class="contest-list">
9
+ {% for contest in contests %}
10
+ <div class="contest-list-item">
11
+ <a href="{{ contest.link }}">
12
+ <img src="{{ contest.img }}" alt="{{ contest.alt }}">
13
+ </a>
14
+ <h2>{{ contest.title }}</h2>
15
+ <p>Closes: {{ contest.deadline }}</p>
16
+ <p class="ible-small">{{ contest.prizes }} Prizes, {{ contest.entries }} Entries</p>
17
+ </div>
18
+ {% endfor %}
19
+ </div>
20
+ </center>
21
+ {% endblock %}
@@ -1,177 +0,0 @@
1
- from flask import render_template, request, abort
2
- from urllib.request import urlopen
3
- from urllib.error import HTTPError
4
- from ..utils.helpers import proxy
5
- from bs4 import BeautifulSoup
6
-
7
- def init_contest_routes(app):
8
- @app.route("/contest/archive/")
9
- def route_contest_archive():
10
- page = 1
11
- if request.args.get("page") is not None:
12
- page = request.args.get("page")
13
-
14
- try:
15
- data = urlopen(f"https://www.instructables.com/contest/archive/?page={page}")
16
- except HTTPError as e:
17
- abort(e.code)
18
-
19
- soup = BeautifulSoup(data.read().decode(), "html.parser")
20
-
21
- main = soup.select("div#contest-archive-wrapper")[0]
22
-
23
- contest_count = main.select("p.contest-count")[0].text
24
-
25
- contest_list = []
26
- for index, year in enumerate(main.select("div.contest-archive-list h2")):
27
- year_list = main.select(
28
- "div.contest-archive-list div.contest-archive-list-year"
29
- )[index]
30
- year_name = year.text
31
- month_list = []
32
- for month in year_list.select("div.contest-archive-list-month"):
33
- month_name = month.select("h3")[0].text
34
- month_contest_list = []
35
- for p in month.select("p"):
36
- date = p.select("span")[0].text
37
- link = p.select("a")[0].get("href")
38
- title = p.select("a")[0].text
39
- month_contest_list.append([date, link, title])
40
- month_list.append([month_name, month_contest_list])
41
- contest_list.append([year_name, month_list])
42
-
43
- pagination = main.select("nav.pagination ul.pagination")[0]
44
-
45
- return render_template(
46
- "archives.html",
47
- title=f"Contest Archives (Page {page})",
48
- page=page,
49
- contest_count=contest_count,
50
- pagination=pagination,
51
- contest_list=contest_list,
52
- )
53
-
54
- @app.route("/contest/<contest>/")
55
- def route_contest(contest):
56
- try:
57
- data = urlopen(f"https://www.instructables.com/contest/{contest}/")
58
- except HTTPError as e:
59
- abort(e.code)
60
-
61
- soup = BeautifulSoup(data.read().decode(), "html.parser")
62
-
63
- title = soup.select('meta[property="og:title"]')[0].get("content")
64
-
65
- body = soup.select("div#contest-wrapper")[0]
66
-
67
- img = proxy(body.select("div#contest-masthead img")[0].get("src"))
68
-
69
- entry_count = body.select("li.entries-nav-btn")[0].text.split(" ")[0]
70
- prizes = body.select("li.prizes-nav-btn")[0].text.split(" ")[0]
71
-
72
- info = body.select("div.contest-body-column-left")[0]
73
- info.select("div#site-announcements-page")[0].decompose()
74
- info.select("h3")[0].decompose()
75
- info.select("div#contest-body-nav")[0].decompose()
76
- info = str(info).replace("https://www.instructables.com", "/")
77
-
78
- body.select("span.contest-entity-count")[0].text
79
-
80
- entry_list = []
81
- for entry in body.select("div.contest-entries-list div.contest-entries-list-ible"):
82
- link = entry.a["href"]
83
- entry_img = proxy(entry.select("a noscript img")[0].get("src"))
84
- entry_title = entry.select("a.ible-title")[0].text
85
- author = entry.select("div span.ible-author a")[0].text
86
- author_link = entry.select("div span.ible-author a")[0].get("href")
87
- channel = entry.select("div span.ible-channel a")[0].text
88
- channel_link = entry.select("div span.ible-channel a")[0].get("href")
89
- views = entry.select(".ible-views")[0].text
90
-
91
- entry_list.append(
92
- {
93
- "link": link,
94
- "entry_img": entry_img,
95
- "entry_title": entry_title,
96
- "author": author,
97
- "author_link": author_link,
98
- "channel": channel,
99
- "channel_link": channel_link,
100
- "views": views,
101
- }
102
- )
103
-
104
- return render_template(
105
- "contest.html",
106
- title=title,
107
- img=img,
108
- entry_count=entry_count,
109
- prizes=prizes,
110
- info=info,
111
- entry_list=entry_list,
112
- )
113
-
114
- @app.route("/contest/")
115
- def route_contests():
116
- try:
117
- data = urlopen("https://www.instructables.com/contest/")
118
- except HTTPError as e:
119
- abort(e.code)
120
-
121
- soup = BeautifulSoup(data.read().decode(), "html.parser")
122
-
123
- contest_count = str(soup.select("p.contest-count")[0])
124
-
125
- contests = []
126
- for contest in soup.select("div#cur-contests div.row-fluid div.contest-banner"):
127
- link = contest.select("div.contest-banner-inner a")[0].get("href")
128
- img = proxy(contest.select("div.contest-banner-inner a img")[0].get("src"))
129
- alt = contest.select("div.contest-banner-inner a img")[0].get("alt")
130
- deadline = contest.select("span.contest-meta-deadline")[0].get("data-deadline")
131
- prizes = contest.select("span.contest-meta-count")[0].text
132
- entries = contest.select("span.contest-meta-count")[1].text
133
-
134
- contests.append(
135
- {
136
- "link": link,
137
- "img": img,
138
- "alt": alt,
139
- "deadline": deadline,
140
- "prizes": prizes,
141
- "entries": entries,
142
- }
143
- )
144
-
145
- closed = []
146
- for display in soup.select("div.contest-winner-display"):
147
- link = display.select("div.contest-banner-inner a")[0].get("href")
148
- img = proxy(display.select("div.contest-banner-inner a img")[0].get("src"))
149
- alt = display.select("div.contest-banner-inner a img")[0].get("alt")
150
- featured_items = []
151
- for featured_item in display.select("ul.featured-items li"):
152
- item_link = featured_item.select("div.ible-thumb a")[0].get("href")
153
- item_img = proxy(featured_item.select("div.ible-thumb a img")[0].get("src"))
154
- item_title = featured_item.select("a.title")[0].text
155
- item_author = featured_item.select("a.author")[0].text
156
- item_author_link = featured_item.select("a.author")[0].get("href")
157
-
158
- featured_items.append(
159
- {
160
- "link": item_link,
161
- "img": item_img,
162
- "title": item_title,
163
- "author": item_author,
164
- "author_link": item_author_link,
165
- }
166
- )
167
- closed.append(
168
- {"link": link, "img": img, "alt": alt, "featured_items": featured_items}
169
- )
170
-
171
- return render_template(
172
- "contests.html",
173
- title="Contests",
174
- contest_count=contest_count,
175
- contests=contests,
176
- closed=closed,
177
- )
@@ -1,31 +0,0 @@
1
- {% extends "base.html" %}
2
-
3
- {% block content %}
4
- <center>
5
- <h1>Past Contests</h1>
6
- <p>{{ contest_count }}</p>
7
- <p><a href="/contest/">See running contests</a></p>
8
- </center>
9
- {% for year in contest_list %}
10
- <div class="archive-year">
11
- <hr>
12
- <h2>{{ year[0] }}</h2>
13
- <hr>
14
- {% for month in year[1] %}
15
- <div class="archive-month-wrapper">
16
- <div class="archive-month">
17
- <h3>{{ month[0] }}</h3>
18
- {% for contest in month[1] %}
19
- <div class="archive">
20
- <p>{{ contest[0] }}<a href="{{ contest[1] }}">{{ contest[2] }}</a></p>
21
- </div>
22
- {% endfor %}
23
- </div>
24
- </div>
25
- {% endfor %}
26
- <br>
27
- </div>
28
- {% endfor %}
29
- <hr>
30
- {{ pagination|safe }}
31
- {% endblock %}
@@ -1,23 +0,0 @@
1
-
2
- {% extends "base.html" %}
3
-
4
- {% block content %}
5
- <center>
6
- <img src="{{ img }}" alt="{{ title }}" style="max-width:98vw;">
7
- <p>{{ entry_count }} Entries, {{ prizes }} Prizes</p>
8
- <br>
9
- {{ info|safe }}
10
- <div class="ible-list">
11
- {% for ible in entries %}
12
- <div class="ible-list-item">
13
- <a href="{{ ible.link }}" style="color:#bbc2cf;">
14
- <img style="max-width:350px;" src="{{ ible.entry_img }}" alt="{{ ible.entry_title }}">
15
- <p>{{ ible.entry_title }}</p>
16
- </a>
17
- <p>by <a href="{{ ible.author_link }}">{{ ible.author }}</a> in <a href="{{ ible.channel_link }}">{{ ible.channel }}</a></p>
18
- <p>{{ ible.views }} Views</p>
19
- </div>
20
- {% endfor %}
21
- </div>
22
- </center>
23
- {% endblock %}
@@ -1,40 +0,0 @@
1
- {% extends "base.html" %}
2
-
3
- {% block content %}
4
- <center>
5
- <h1>{{ title }}</h1>
6
- {{ contest_count|safe }}
7
- <br>
8
- <div class="contest-list">
9
- {% for contest in contests %}
10
- <div class="contest-list-item">
11
- <a href="{{ contest.link }}">
12
- <img src="{{ contest.img }}" alt="{{ contest.alt }}" style="max-width:500px;">
13
- </a>
14
- <p>Closes {{ contest.deadline }}</p>
15
- <p class="ible-small">{{ contest.prizes }} Prizes, {{ contest.entries }} Entries</p>
16
- </div>
17
- {% endfor %}
18
- </div>
19
- <div class="closed-contests" id="contest-winners">
20
- <h2>Winner's Circle</h2>
21
- {% for closed in closed %}
22
- <div class="closed-contest">
23
- <a href="{{ closed.link }}"><img class="closed-contest-contest" src="{{ closed.img }}"
24
- alt="{{ closed.alt }}"></a>
25
- {% for featured_items in closed.featured_items %}
26
- <div class="closed-contest-winner">
27
- <a href="{{ featured_items.link }}">
28
- <img class="closed-contest-winner-img" src="{{ featured_items.img }}"
29
- alt="{{ featured_items.title}}">
30
- <br>
31
- <b>{{ featured_items.title }}</b>
32
- </a>
33
- <p>by <a href="{{ featured_items.author_link }}">{{ featured_items.author }}</a></p>
34
- </div>
35
- {% endfor %}
36
- </div>
37
- {% endfor %}
38
- </div>
39
- </center>
40
- {% endblock %}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes