structables 0.2.1__tar.gz → 0.3.0__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 (49) hide show
  1. {structables-0.2.1 → structables-0.3.0}/PKG-INFO +8 -2
  2. {structables-0.2.1 → structables-0.3.0}/README.md +7 -1
  3. {structables-0.2.1 → structables-0.3.0}/pyproject.toml +1 -1
  4. structables-0.3.0/src/structables/config.py +13 -0
  5. structables-0.3.0/src/structables/main.py +20 -0
  6. structables-0.3.0/src/structables/routes/__init__.py +12 -0
  7. structables-0.3.0/src/structables/routes/category.py +76 -0
  8. structables-0.3.0/src/structables/routes/contest.py +177 -0
  9. structables-0.3.0/src/structables/routes/main.py +362 -0
  10. structables-0.3.0/src/structables/routes/member.py +124 -0
  11. structables-0.3.0/src/structables/routes/proxy.py +50 -0
  12. {structables-0.2.1 → structables-0.3.0}/src/structables/static/css/style.css +31 -0
  13. structables-0.3.0/src/structables/templates/sitemap.html +27 -0
  14. structables-0.3.0/src/structables/utils/data.py +56 -0
  15. structables-0.3.0/src/structables/utils/helpers.py +451 -0
  16. structables-0.2.1/src/structables/main.py +0 -1282
  17. structables-0.2.1/src/structables/templates/sitemap.html +0 -23
  18. {structables-0.2.1 → structables-0.3.0}/.gitignore +0 -0
  19. {structables-0.2.1 → structables-0.3.0}/LICENSE +0 -0
  20. {structables-0.2.1 → structables-0.3.0}/cronjob.txt +0 -0
  21. {structables-0.2.1 → structables-0.3.0}/src/structables/__init__.py +0 -0
  22. {structables-0.2.1 → structables-0.3.0}/src/structables/static/dist/css/bootstrap.min.css +0 -0
  23. {structables-0.2.1 → structables-0.3.0}/src/structables/static/dist/css/bootstrap.min.css.map +0 -0
  24. {structables-0.2.1 → structables-0.3.0}/src/structables/static/dist/js/bootstrap.bundle.min.js +0 -0
  25. {structables-0.2.1 → structables-0.3.0}/src/structables/static/dist/js/bootstrap.bundle.min.js.map +0 -0
  26. {structables-0.2.1 → structables-0.3.0}/src/structables/static/img/logo.png +0 -0
  27. {structables-0.2.1 → structables-0.3.0}/src/structables/static/img/logo_lg.png +0 -0
  28. {structables-0.2.1 → structables-0.3.0}/src/structables/static/img/magnifying-glass-solid.svg +0 -0
  29. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/400.html +0 -0
  30. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/404.html +0 -0
  31. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/429.html +0 -0
  32. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/500.html +0 -0
  33. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/archives.html +0 -0
  34. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/article-review.html +0 -0
  35. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/article.html +0 -0
  36. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/base.html +0 -0
  37. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/category.html +0 -0
  38. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/collection.html +0 -0
  39. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/contest.html +0 -0
  40. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/contests.html +0 -0
  41. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/footer.html +0 -0
  42. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/header.html +0 -0
  43. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/iframe.html +0 -0
  44. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/index.html +0 -0
  45. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/member-instructables.html +0 -0
  46. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/member.html +0 -0
  47. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/privacypolicy.html +0 -0
  48. {structables-0.2.1 → structables-0.3.0}/src/structables/templates/projects.html +0 -0
  49. {structables-0.2.1 → structables-0.3.0}/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.1
3
+ Version: 0.3.0
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
@@ -683,7 +683,7 @@ Description-Content-Type: text/markdown
683
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>.
684
684
 
685
685
  [![Support Private.coffee!](https://shields.private.coffee/badge/private.coffee-support%20us!-pink?logo=coffeescript)](https://private.coffee)
686
- [![Matrix](https://shields.private.coffee/badge/Matrix-join%20us!-blue?logo=matrix)](https://matrix.to/#/#structables:private.coffee)
686
+ [![Matrix](https://shields.private.coffee/badge/Matrix-join%20us!-blue?logo=matrix)](https://matrix.pcof.fi/#/#structables:private.coffee)
687
687
  [![PyPI](https://shields.private.coffee/pypi/v/structables)](https://pypi.org/project/structables/)
688
688
  [![PyPI - Python Version](https://shields.private.coffee/pypi/pyversions/structables)](https://pypi.org/project/structables/)
689
689
  [![PyPI - License](https://shields.private.coffee/pypi/l/structables)](https://pypi.org/project/structables/)
@@ -697,6 +697,12 @@ An open source alternative front-end to Instructables. This is a fork of <a href
697
697
 
698
698
  To add your own instance to this list, please open a pull request or issue.
699
699
 
700
+ ## Opening Issues
701
+
702
+ If you're having problems using Structables, or if you have ideas or feedback for us, feel free to open an issue in the [Private.coffee Git](https://git.private.coffee/PrivateCoffee/structables/issues) or on [Github](https://github.com/PrivateCoffee/structables/issues).
703
+
704
+ Of course, you can also join our [Matrix room](https://matrix.pcof.fi/#/#structables:private.coffee) to discuss your ideas with us.
705
+
700
706
  ## Run your own instance
701
707
 
702
708
  ### Production
@@ -5,7 +5,7 @@
5
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>.
6
6
 
7
7
  [![Support Private.coffee!](https://shields.private.coffee/badge/private.coffee-support%20us!-pink?logo=coffeescript)](https://private.coffee)
8
- [![Matrix](https://shields.private.coffee/badge/Matrix-join%20us!-blue?logo=matrix)](https://matrix.to/#/#structables:private.coffee)
8
+ [![Matrix](https://shields.private.coffee/badge/Matrix-join%20us!-blue?logo=matrix)](https://matrix.pcof.fi/#/#structables:private.coffee)
9
9
  [![PyPI](https://shields.private.coffee/pypi/v/structables)](https://pypi.org/project/structables/)
10
10
  [![PyPI - Python Version](https://shields.private.coffee/pypi/pyversions/structables)](https://pypi.org/project/structables/)
11
11
  [![PyPI - License](https://shields.private.coffee/pypi/l/structables)](https://pypi.org/project/structables/)
@@ -19,6 +19,12 @@ An open source alternative front-end to Instructables. This is a fork of <a href
19
19
 
20
20
  To add your own instance to this list, please open a pull request or issue.
21
21
 
22
+ ## Opening Issues
23
+
24
+ If you're having problems using Structables, or if you have ideas or feedback for us, feel free to open an issue in the [Private.coffee Git](https://git.private.coffee/PrivateCoffee/structables/issues) or on [Github](https://github.com/PrivateCoffee/structables/issues).
25
+
26
+ Of course, you can also join our [Matrix room](https://matrix.pcof.fi/#/#structables:private.coffee) to discuss your ideas with us.
27
+
22
28
  ## Run your own instance
23
29
 
24
30
  ### Production
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "structables"
7
- version = "0.2.1"
7
+ version = "0.3.0"
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,20 @@
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
+ if __name__ == "__main__":
17
+ app.run(port=app.config['PORT'], host=app.config['LISTEN_HOST'], debug=app.config['DEBUG'])
18
+
19
+ # Initialize data when the server starts
20
+ 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
+ )