structables 0.2.2__tar.gz → 0.3.1__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 (48) hide show
  1. {structables-0.2.2 → structables-0.3.1}/PKG-INFO +2 -2
  2. {structables-0.2.2 → structables-0.3.1}/README.md +1 -1
  3. {structables-0.2.2 → structables-0.3.1}/pyproject.toml +1 -1
  4. structables-0.3.1/src/structables/config.py +13 -0
  5. structables-0.3.1/src/structables/main.py +23 -0
  6. structables-0.3.1/src/structables/routes/__init__.py +12 -0
  7. structables-0.3.1/src/structables/routes/category.py +76 -0
  8. structables-0.3.1/src/structables/routes/contest.py +177 -0
  9. structables-0.3.1/src/structables/routes/main.py +362 -0
  10. structables-0.3.1/src/structables/routes/member.py +124 -0
  11. structables-0.3.1/src/structables/routes/proxy.py +50 -0
  12. structables-0.3.1/src/structables/utils/data.py +56 -0
  13. structables-0.3.1/src/structables/utils/helpers.py +451 -0
  14. structables-0.2.2/src/structables/main.py +0 -1313
  15. {structables-0.2.2 → structables-0.3.1}/.gitignore +0 -0
  16. {structables-0.2.2 → structables-0.3.1}/LICENSE +0 -0
  17. {structables-0.2.2 → structables-0.3.1}/cronjob.txt +0 -0
  18. {structables-0.2.2 → structables-0.3.1}/src/structables/__init__.py +0 -0
  19. {structables-0.2.2 → structables-0.3.1}/src/structables/static/css/style.css +0 -0
  20. {structables-0.2.2 → structables-0.3.1}/src/structables/static/dist/css/bootstrap.min.css +0 -0
  21. {structables-0.2.2 → structables-0.3.1}/src/structables/static/dist/css/bootstrap.min.css.map +0 -0
  22. {structables-0.2.2 → structables-0.3.1}/src/structables/static/dist/js/bootstrap.bundle.min.js +0 -0
  23. {structables-0.2.2 → structables-0.3.1}/src/structables/static/dist/js/bootstrap.bundle.min.js.map +0 -0
  24. {structables-0.2.2 → structables-0.3.1}/src/structables/static/img/logo.png +0 -0
  25. {structables-0.2.2 → structables-0.3.1}/src/structables/static/img/logo_lg.png +0 -0
  26. {structables-0.2.2 → structables-0.3.1}/src/structables/static/img/magnifying-glass-solid.svg +0 -0
  27. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/400.html +0 -0
  28. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/404.html +0 -0
  29. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/429.html +0 -0
  30. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/500.html +0 -0
  31. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/archives.html +0 -0
  32. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/article-review.html +0 -0
  33. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/article.html +0 -0
  34. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/base.html +0 -0
  35. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/category.html +0 -0
  36. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/collection.html +0 -0
  37. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/contest.html +0 -0
  38. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/contests.html +0 -0
  39. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/footer.html +0 -0
  40. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/header.html +0 -0
  41. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/iframe.html +0 -0
  42. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/index.html +0 -0
  43. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/member-instructables.html +0 -0
  44. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/member.html +0 -0
  45. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/privacypolicy.html +0 -0
  46. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/projects.html +0 -0
  47. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/sitemap.html +0 -0
  48. {structables-0.2.2 → structables-0.3.1}/src/structables/templates/style.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: structables
3
- Version: 0.2.2
3
+ Version: 0.3.1
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
@@ -680,7 +680,7 @@ Description-Content-Type: text/markdown
680
680
 
681
681
  # Structables
682
682
 
683
- An open source alternative front-end to Instructables. This is a fork of <a href="https://codeberg.org/indestructables/indestructables">snowcatridge10's Indestructables</a> to get rid of Selenium. Indestructables itself is a fork of <a href="https://git.vern.cc/cobra/Destructables">Cobra's Destructables</a>.
683
+ An open source alternative front-end to Instructables. This is a fork of <a href="https://codeberg.org/indestructables/indestructables">snowcatridge10's Indestructables</a>, which itself is a fork of <a href="https://git.vern.cc/cobra/Destructables">Cobra's Destructables</a>.
684
684
 
685
685
  [![Support Private.coffee!](https://shields.private.coffee/badge/private.coffee-support%20us!-pink?logo=coffeescript)](https://private.coffee)
686
686
  [![Matrix](https://shields.private.coffee/badge/Matrix-join%20us!-blue?logo=matrix)](https://matrix.pcof.fi/#/#structables:private.coffee)
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Structables
4
4
 
5
- An open source alternative front-end to Instructables. This is a fork of <a href="https://codeberg.org/indestructables/indestructables">snowcatridge10's Indestructables</a> to get rid of Selenium. Indestructables itself is a fork of <a href="https://git.vern.cc/cobra/Destructables">Cobra's Destructables</a>.
5
+ An open source alternative front-end to Instructables. This is a fork of <a href="https://codeberg.org/indestructables/indestructables">snowcatridge10's Indestructables</a>, which itself is a fork of <a href="https://git.vern.cc/cobra/Destructables">Cobra's Destructables</a>.
6
6
 
7
7
  [![Support Private.coffee!](https://shields.private.coffee/badge/private.coffee-support%20us!-pink?logo=coffeescript)](https://private.coffee)
8
8
  [![Matrix](https://shields.private.coffee/badge/Matrix-join%20us!-blue?logo=matrix)](https://matrix.pcof.fi/#/#structables:private.coffee)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "structables"
7
- version = "0.2.2"
7
+ version = "0.3.1"
8
8
  authors = [
9
9
  { name="Private.coffee Team", email="support@private.coffee" },
10
10
  ]
@@ -0,0 +1,13 @@
1
+ import os
2
+
3
+ class Config:
4
+ DEBUG = os.environ.get("FLASK_DEBUG", os.environ.get("STRUCTABLES_DEBUG", False))
5
+ PORT = int(os.environ.get("STRUCTABLES_PORT", 8002))
6
+ LISTEN_HOST = os.environ.get("STRUCTABLES_LISTEN_HOST", "127.0.0.1")
7
+ INVIDIOUS = os.environ.get("STRUCTABLES_INVIDIOUS")
8
+ UNSAFE = os.environ.get("STRUCTABLES_UNSAFE", False)
9
+ PRIVACY_FILE = os.environ.get("STRUCTABLES_PRIVACY_FILE", "privacy.txt")
10
+
11
+ @staticmethod
12
+ def init_app(app):
13
+ pass
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env python
2
+
3
+ from flask import Flask
4
+
5
+ from .config import Config
6
+ from .routes import init_routes
7
+ from .utils.data import update_data
8
+ from .utils.helpers import get_typesense_api_key
9
+
10
+ app = Flask(__name__, template_folder="templates", static_folder="static")
11
+ app.config.from_object(Config)
12
+ app.typesense_api_key = get_typesense_api_key()
13
+
14
+ init_routes(app)
15
+
16
+ def main():
17
+ app.run(port=app.config['PORT'], host=app.config['LISTEN_HOST'], debug=app.config['DEBUG'])
18
+
19
+ if __name__ == "__main__":
20
+ main()
21
+
22
+ # Initialize data when the server starts
23
+ update_data(app)
@@ -0,0 +1,12 @@
1
+ from .main import init_main_routes
2
+ from .category import init_category_routes
3
+ from .member import init_member_routes
4
+ from .proxy import init_proxy_routes
5
+ from .contest import init_contest_routes
6
+
7
+ def init_routes(app):
8
+ init_main_routes(app)
9
+ init_category_routes(app)
10
+ init_member_routes(app)
11
+ init_proxy_routes(app)
12
+ init_contest_routes(app)
@@ -0,0 +1,76 @@
1
+ from flask import redirect
2
+ from werkzeug.exceptions import NotFound
3
+ from ..utils.helpers import project_list, category_page
4
+
5
+
6
+ def init_category_routes(app):
7
+ @app.route("/<category>/<channel>/projects/")
8
+ def route_channel_projects(category, channel):
9
+ return project_list(app, channel.title())
10
+
11
+ @app.route("/<category>/<channel>/projects/<sort>/")
12
+ def route_channel_projects_sort(category, channel, sort):
13
+ return project_list(
14
+ app,
15
+ channel.title(),
16
+ " Sorted by " + sort.title(),
17
+ )
18
+
19
+ @app.route("/<category>/projects/")
20
+ def route_category_projects(category):
21
+ return project_list(app, category.title())
22
+
23
+ @app.route("/<category>/projects/<sort>/")
24
+ def route_category_projects_sort(category, sort):
25
+ return project_list(app, category.title(), " Sorted by " + sort.title())
26
+
27
+ @app.route("/projects/")
28
+ def route_projects():
29
+ return project_list(app, "")
30
+
31
+ @app.route("/projects/<sort>/")
32
+ def route_projects_sort(sort):
33
+ return project_list(app, "", " Sorted by " + sort.title())
34
+
35
+ @app.route("/circuits/")
36
+ def route_circuits():
37
+ return category_page(app, "Circuits")
38
+
39
+ @app.route("/workshop/")
40
+ def route_workshop():
41
+ return category_page(app, "Workshop")
42
+
43
+ @app.route("/craft/")
44
+ def route_craft():
45
+ return category_page(app, "Craft")
46
+
47
+ @app.route("/cooking/")
48
+ def route_cooking():
49
+ return category_page(app, "Cooking")
50
+
51
+ @app.route("/living/")
52
+ def route_living():
53
+ return category_page(app, "Living")
54
+
55
+ @app.route("/outside/")
56
+ def route_outside():
57
+ return category_page(app, "Outside")
58
+
59
+ @app.route("/teachers/")
60
+ def route_teachers():
61
+ return category_page(app, "Teachers", True)
62
+
63
+ @app.route("/<category>/<channel>/")
64
+ def route_channel_redirect(category, channel):
65
+ if (
66
+ category == "circuits"
67
+ or category == "workshop"
68
+ or category == "craft"
69
+ or category == "cooking"
70
+ or category == "living"
71
+ or category == "outside"
72
+ or category == "teachers"
73
+ ):
74
+ return redirect(f"/{category}/{channel}/projects/", 307)
75
+ else:
76
+ raise NotFound()
@@ -0,0 +1,177 @@
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
+ )