diff --git a/src/structables/routes/contest.py b/src/structables/routes/contest.py index 48fef2f..3c88e13 100644 --- a/src/structables/routes/contest.py +++ b/src/structables/routes/contest.py @@ -3,50 +3,59 @@ from urllib.request import urlopen from urllib.error import HTTPError from ..utils.helpers import proxy from bs4 import BeautifulSoup +import json + def init_contest_routes(app): @app.route("/contest/archive/") def route_contest_archive(): - page = 1 - if request.args.get("page") is not None: - page = request.args.get("page") + # Default pagination settings + limit = 10 + page = request.args.get("page", default=1, type=int) + offset = (page - 1) * limit try: - data = urlopen(f"https://www.instructables.com/contest/archive/?page={page}") + # Fetch data using urlopen + url = f"https://www.instructables.com/json-api/getClosedContests?limit={limit}&offset={offset}" + response = urlopen(url) + data = json.loads(response.read().decode()) except HTTPError as e: abort(e.code) + except Exception as e: + abort(500) # Handle other exceptions like JSON decode errors - soup = BeautifulSoup(data.read().decode(), "html.parser") - - main = soup.select("div#contest-archive-wrapper")[0] - - contest_count = main.select("p.contest-count")[0].text + contests = data.get("contests", []) + full_list_size = data.get("fullListSize", 0) contest_list = [] - for index, year in enumerate(main.select("div.contest-archive-list h2")): - year_list = main.select( - "div.contest-archive-list div.contest-archive-list-year" - )[index] - year_name = year.text - month_list = [] - for month in year_list.select("div.contest-archive-list-month"): - month_name = month.select("h3")[0].text - month_contest_list = [] - for p in month.select("p"): - date = p.select("span")[0].text - link = p.select("a")[0].get("href") - title = p.select("a")[0].text - month_contest_list.append([date, link, title]) - month_list.append([month_name, month_contest_list]) - contest_list.append([year_name, month_list]) + for contest in contests: + contest_details = { + "title": contest["title"], + "link": f"https://www.instructables.com/{contest['urlString']}", + "deadline": contest["deadline"], + "startDate": contest["startDate"], + "numEntries": contest["numEntries"], + "state": contest["state"], + "bannerUrl": contest["bannerUrlMedium"], + } + contest_list.append(contest_details) - pagination = main.select("nav.pagination ul.pagination")[0] + # Calculate total pages + total_pages = (full_list_size + limit - 1) // limit + + # Create pagination + pagination = { + "current_page": page, + "total_pages": total_pages, + "has_prev": page > 1, + "has_next": page < total_pages, + "limit": limit + } return render_template( "archives.html", title=f"Contest Archives (Page {page})", page=page, - contest_count=contest_count, pagination=pagination, contest_list=contest_list, ) @@ -78,7 +87,9 @@ def init_contest_routes(app): body.select("span.contest-entity-count")[0].text entry_list = [] - for entry in body.select("div.contest-entries-list div.contest-entries-list-ible"): + for entry in body.select( + "div.contest-entries-list div.contest-entries-list-ible" + ): link = entry.a["href"] entry_img = proxy(entry.select("a noscript img")[0].get("src")) entry_title = entry.select("a.ible-title")[0].text @@ -120,14 +131,16 @@ def init_contest_routes(app): soup = BeautifulSoup(data.read().decode(), "html.parser") - contest_count = str(soup.select("p.contest-count")[0]) + contest_count = "0" contests = [] for contest in soup.select("div#cur-contests div.row-fluid div.contest-banner"): link = contest.select("div.contest-banner-inner a")[0].get("href") img = proxy(contest.select("div.contest-banner-inner a img")[0].get("src")) alt = contest.select("div.contest-banner-inner a img")[0].get("alt") - deadline = contest.select("span.contest-meta-deadline")[0].get("data-deadline") + deadline = contest.select("span.contest-meta-deadline")[0].get( + "data-deadline" + ) prizes = contest.select("span.contest-meta-count")[0].text entries = contest.select("span.contest-meta-count")[1].text @@ -150,7 +163,9 @@ def init_contest_routes(app): featured_items = [] for featured_item in display.select("ul.featured-items li"): item_link = featured_item.select("div.ible-thumb a")[0].get("href") - item_img = proxy(featured_item.select("div.ible-thumb a img")[0].get("src")) + item_img = proxy( + featured_item.select("div.ible-thumb a img")[0].get("src") + ) item_title = featured_item.select("a.title")[0].text item_author = featured_item.select("a.author")[0].text item_author_link = featured_item.select("a.author")[0].get("href") @@ -174,4 +189,4 @@ def init_contest_routes(app): contest_count=contest_count, contests=contests, closed=closed, - ) \ No newline at end of file + ) diff --git a/src/structables/static/css/style.css b/src/structables/static/css/style.css index 22d85b5..b3c1041 100644 --- a/src/structables/static/css/style.css +++ b/src/structables/static/css/style.css @@ -130,144 +130,67 @@ blockquote { } } -.ibles { - display: inline-block; - vertical-align: top; +.contest-list { + display: flex; + flex-direction: column; + gap: 20px; } -.ible-small { - font-size: 0.7em; - font-weight: thin; - line-height: 1em; -} - -.step-section { - background-color: var(--primary-bg); +.contest-item { + background-color: #fff; + padding: 20px; border: 1px solid var(--border-color); border-radius: 8px; - box-shadow: 0 2px 4px var(--shadow-color); - padding: 1.5rem; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } -.step-header h2 { - font-size: 2em; - color: var(--heading-color); +.contest-item img { + max-width: 100%; + height: auto; + border-radius: 4px; + margin-top: 10px; } -.step-images img, -.step-videos video, -.step-iframes iframe { - border-radius: 8px; - box-shadow: 0 2px 4px var(--shadow-color); -} - -.step-text { - font-size: 1.1em; -} - -.reply-button, -.replies { - display: none; -} - -.reply-button+label { - position: relative; - display: block; - cursor: pointer; -} - -input.reply-button:checked+label+.replies { +.pagination { display: flex; - flex-direction: column; - gap: 1rem; - margin-top: 1rem; -} - -.member-list { - display: inline-block; - max-width: 200px; - vertical-align: top; -} - -.ible-list-item { - display: inline-block; - max-width: 350px; - vertical-align: top; - margin-bottom: 2rem; -} - -.contest-list-item { - display: inline-block; - max-width: 500px; - vertical-align: top; - margin-bottom: 2rem; -} - -.archive-month-wrapper { - display: inline-block; - width: 30vw; - vertical-align: top; -} - -.archive-month { - display: flex; - flex-direction: column; - gap: -10px; - margin-bottom: 1rem; - justify-content: space-between; -} - -.archive { - margin-bottom: -20px; -} - -ul.pagination { - display: flex; - justify-content: space-around; - padding: 0 33vw; + justify-content: center; + padding: 10px 0; list-style-type: none; +} + +.pagination-list { + display: flex; align-items: center; + gap: 10px; } -ul.pagination li.active a, -ul.pagination li.disabled a, -ul.pagination li.active a:hover, -ul.pagination li.disabled a:hover { - color: #bbc2cf; +.pagination-list li { + display: inline-block; +} + +.pagination-list a { + color: var(--main-color); text-decoration: none; + padding: 5px 10px; + border-radius: 4px; + border: 1px solid transparent; + transition: all 0.3s ease; } -.closed-contest-contest { - object-fit: cover; - width: 33vw; - height: 15vw; - display: inline-block; - vertical-align: top; - padding: 0 10px; +.pagination-list a:hover { + border-color: var(--link-color); + color: var(--link-color); } -.closed-contest-winner, -.closed-contest-winner-img { - width: 15vw; - display: inline-block; - vertical-align: top; - padding: 0 10px; - font-size: 0.8em; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; +.pagination-list li.active a { + background-color: var(--link-color); + color: #fff; + border-color: var(--link-color); } -.sitemap-group { - margin-top: 2em; - display: inline-block; - width: 30vw; - text-align: left; - vertical-align: top; -} - -.sitemap-group h2 { - text-align: center; +.pagination-list li.disabled a { + color: #ccc; + cursor: not-allowed; } .container { @@ -280,7 +203,6 @@ header { .go_here_link { background-color: #4caf50; - /* Green */ border: none; color: white; padding: 15px 32px; @@ -299,10 +221,6 @@ header { overflow-wrap: break-word; } -.navbar-logo { - height: 64px; -} - .img-fluid { border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); @@ -322,7 +240,17 @@ iframe { color: #6c757d !important; } +.sitemap-group { + margin-top: 2em; + display: inline-block; + width: 30vw; + text-align: left; + vertical-align: top; +} +.sitemap-group h2 { + text-align: center; +} .sitemap-group .card { background-color: var(--primary-bg); diff --git a/src/structables/templates/archives.html b/src/structables/templates/archives.html index 21d03eb..73da09b 100644 --- a/src/structables/templates/archives.html +++ b/src/structables/templates/archives.html @@ -1,31 +1,38 @@ {% extends "base.html" %} {% block content %} -
-

Past Contests

-

{{ contest_count }}

-

See running contests

-
- {% for year in contest_list %} -
-
-

{{ year[0] }}

-
- {% for month in year[1] %} -
-
-

{{ month[0] }}

- {% for contest in month[1] %} -
-

{{ contest[0] }}{{ contest[2] }}

-
- {% endfor %} -
-
- {% endfor %} -
-
- {% endfor %} -
- {{ pagination|safe }} -{% endblock %} \ No newline at end of file +
+

Past Contests

+

Total Contests: {{ pagination.total_pages * pagination.limit }}

+

See running contests

+
+
+ {% for contest in contest_list %} +
+
+

{{ contest.title }}

+

+ {{ contest.title }}
+ Start Date: {{ contest.startDate }}
+ Deadline: {{ contest.deadline }}
+ Entries: {{ contest.numEntries }}
+ Status: {{ contest.state }}
+

+ {{ contest.title }} banner +
+
+ {% endfor %} +
+
+ +{% endblock %}