Skip to content

Commit 66c454b

Browse files
authored
Merge branch 'master' into dependabot/pip/werkzeug-3.0.6
2 parents 6636f1f + 1f9fef9 commit 66c454b

File tree

26 files changed

+294
-2117
lines changed

26 files changed

+294
-2117
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ translations_new.json
166166
*.db-wal
167167
*.db-shm
168168
*.csv
169+
*.bak
169170
config.json
170171
czech.json
171172
temp

App.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from blueprints.api import ApiController
3030
from blueprints.rsspage import RssPageController
3131
from blueprints.oauth import OauthController
32+
from blueprints.embed import EmbedController
3233

3334
from extensions import login_manager, dbs, sched, oauth, rss, webhook
3435

@@ -165,9 +166,12 @@ def extensions_init() -> None:
165166
app.register_blueprint(ApiController)
166167
app.register_blueprint(OauthController)
167168
app.register_blueprint(RssPageController)
169+
app.register_blueprint(EmbedController)
168170

169171
# Create the admin user
170172
user_init()
173+
174+
# Load extensions and enable integrations based on config
171175
extensions_init()
172176

173177
# Force oauthlib to allow insecure transport when debugging

blueprints/articles.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Builtins
22
from datetime import datetime
3+
from http import HTTPStatus
34
from logging import info
45

56
# External
@@ -77,7 +78,7 @@ def edit_article(aid: int):
7778

7879
article = dbs.get_article(aid)
7980
if not article:
80-
abort(404)
81+
abort(HTTPStatus.NOT_FOUND)
8182

8283
if request.method == "GET":
8384
fdata = {'title': article.name, 'words': article.words, 'bonus': article.bonus, 'link': article.link, 'translator': article.author.nickname}

blueprints/auth.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
def login():
1818

1919
if request.method == "GET":
20-
session['login_next'] = request.referrer
20+
# Saves the last URL, but prevents the user from staying on the logging page
21+
# if the first login attempt fails
22+
if not request.referrer.endswith('/login'):
23+
session['login_next'] = request.referrer
2124
return render_template('auth/login.j2', form=LoginForm())
2225

2326
form = LoginForm()
@@ -34,6 +37,7 @@ def login():
3437
return redirect(url_for('UserAuth.pw_change'))
3538
login_user(user)
3639
referrer = session['login_next']
40+
info(referrer)
3741
del session['login_next']
3842
return redirect(referrer or url_for('index'))
3943

blueprints/debug.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
from logging import warning
2-
from flask import Blueprint, redirect, url_for, current_app, request
1+
from http import HTTPStatus
2+
from logging import critical, warning, error
3+
from flask import Blueprint, redirect, url_for, current_app, request, render_template, send_from_directory, flash, abort
34
from flask_login import login_required, current_user
5+
from datetime import datetime
46

5-
from extensions import dbs, sched
7+
from extensions import sched, webhook
68

79
DebugTools = Blueprint('DebugTools', __name__)
810

@@ -15,16 +17,49 @@ def log_debug_access():
1517
@login_required
1618
def nickupdate():
1719
sched.run_job('Fetch nicknames')
18-
return redirect(url_for('index'))
20+
flash("Aktualizace spuštěna na pozadí!")
21+
return redirect(request.referrer or url_for('index'))
1922

2023
@DebugTools.route('/debug/avupdate')
2124
@login_required
2225
def avdownload():
2326
sched.run_job('Download avatars')
24-
return redirect(url_for('index'))
27+
flash("Aktualizace spuštěna na pozadí!")
28+
return redirect(request.referrer or url_for('index'))
2529

2630
@DebugTools.route('/debug/rssupdate')
2731
@login_required
2832
def updaterss():
2933
sched.run_job('Fetch RSS updates')
30-
return redirect(url_for('index'))
34+
flash("Aktualizace spuštěna na pozadí!")
35+
return redirect(request.referrer or url_for('index'))
36+
37+
@DebugTools.route('/debug')
38+
def debug_index():
39+
return render_template('debug/tools.j2')
40+
41+
@DebugTools.route('/debug/test_webhook')
42+
def webhook_testing():
43+
try:
44+
webhook.send_text('TEST MESSAGE')
45+
flash("Testovací webhook odeslán!")
46+
except Exception as e:
47+
flash("Testovací webhook se nepodařilo odeslat (zkontrolujte logy)")
48+
error(f"Sending test webhook failed with error ({str(e)})")
49+
return redirect(request.referrer or url_for('index'))
50+
51+
@DebugTools.route('/debug/db/export')
52+
def export_database():
53+
download_name=datetime.strftime(datetime.now(), 'scp_%d_%m_%Y.db')
54+
flash("Databáze exportována!")
55+
return send_from_directory('data', 'scp.db', as_attachment=True, download_name=download_name)
56+
57+
@DebugTools.route('/debug/raise_error')
58+
def raise_error():
59+
error("Error handling test")
60+
abort(HTTPStatus.INTERNAL_SERVER_ERROR)
61+
62+
@DebugTools.route('/debug/raise_critical')
63+
def raise_critical_error():
64+
critical("Critical error handling test")
65+
abort(HTTPStatus.INTERNAL_SERVER_ERROR)

blueprints/embed.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from http import HTTPStatus
2+
from flask import render_template, Blueprint, abort, request
3+
from extensions import dbs
4+
from enum import StrEnum
5+
from os import getcwd, path, listdir
6+
7+
EmbedController = Blueprint('EmbedController', __name__)
8+
EMBED_TEMPLATE_PATH = path.join(getcwd(), 'templates', 'embeds')
9+
10+
class EmbedType(StrEnum):
11+
TRANSLATOR = "translator"
12+
WRITER = "writer"
13+
CORRECTOR = "corrector"
14+
15+
# TODO: Change this to a class-based blueprint
16+
writer_themes_installed = list()
17+
translator_themes_installed = list()
18+
19+
# "record_once" runs the function only when the blueprint is first registered, don't think they could have chosen a worse name
20+
# The record function takes the BlueprintSetupState object but we discard it here as we don't need it
21+
@EmbedController.record_once
22+
def on_blueprint_load(_):
23+
global writer_themes_installed
24+
global translator_themes_installed
25+
# Build a list of themes from files in the templates directory
26+
writer_themes_installed = [theme.removesuffix('.j2') for theme in listdir(path.join(EMBED_TEMPLATE_PATH, 'writer'))]
27+
translator_themes_installed = [theme.removesuffix('.j2') for theme in listdir(path.join(EMBED_TEMPLATE_PATH, 'translator'))]
28+
29+
def get_template(template_type: EmbedType, theme: str = "default"):
30+
if path.exists(path.join(EMBED_TEMPLATE_PATH, template_type, f"{theme}.j2")):
31+
return f"embeds/{template_type}/{theme}.j2"
32+
else:
33+
# Don't abort on invalid theme, just use the default
34+
return f"embeds/{template_type}/default.j2"
35+
36+
@EmbedController.route('/user/<int:uid>/embed', methods=["GET"])
37+
def user_badge(uid: int):
38+
embed_type = request.args.get("type", type=str, default=EmbedType.TRANSLATOR)
39+
if embed_type not in list(EmbedType): abort(HTTPStatus.BAD_REQUEST) # Abort on invalid type
40+
embed_theme = request.args.get("theme", type=str, default="default")
41+
user = dbs.get_user(uid) or abort(HTTPStatus.NOT_FOUND) # Abort on invalid user
42+
stats = dbs.get_user_stats(uid)
43+
if embed_type == EmbedType.TRANSLATOR:
44+
last = dbs.get_last_article(uid, False)
45+
else:
46+
last = dbs.get_last_article(uid, True)
47+
return render_template(get_template(embed_type, embed_theme), user=user, stats=stats, last=last)

blueprints/users.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from http import HTTPStatus
12
from flask import Blueprint, url_for, redirect, session, request, render_template, abort, flash
23
from forms import NewUserForm, EditUserForm
34
from flask_login import current_user, login_required
@@ -41,7 +42,7 @@ def add_user():
4142
@UserController.route('/user/<int:uid>/edit', methods=["GET", "POST"])
4243
@login_required
4344
def edit_user(uid: int):
44-
user = dbs.get_user(uid) or abort(404)
45+
user = dbs.get_user(uid) or abort(HTTPStatus.NOT_FOUND)
4546

4647
if request.method == "GET":
4748
fdata = {'nickname': user.nickname, 'wikidot': user.wikidot, 'discord': user.discord, 'login': int(user.password is not None)}
@@ -60,7 +61,7 @@ def edit_user(uid: int):
6061
def user(uid: int):
6162
sort = request.args.get('sort', 'latest', str)
6263
page = request.args.get('p', 0, int)
63-
user = dbs.get_user(uid) or abort(404)
64+
user = dbs.get_user(uid) or abort(HTTPStatus.NOT_FOUND)
6465
corrections = dbs.get_corrections_by_user(uid)
6566
translations = dbs.get_translations_by_user(uid, sort, page)
6667
originals = dbs.get_originals_by_user(uid)
@@ -69,9 +70,10 @@ def user(uid: int):
6970
@UserController.route('/user/<int:uid>/delete', methods=["POST", "GET"])
7071
@login_required
7172
def delete_user(uid: int):
72-
name = dbs.get_user(uid).nickname
73+
user = dbs.get_user(uid) or abort(HTTPStatus.NOT_FOUND)
74+
name = user.nickname
7375
dbs.delete_user(uid)
7476
info(f"User {name} deleted by {current_user.nickname} (ID: {current_user.uid})")
7577
flash(f'Uživatel {name} smazán')
7678

77-
return redirect(url_for('index'))
79+
return redirect(url_for('index'))

connectors/rss.py

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -79,43 +79,6 @@ def get_rss_update_type(update: dict) -> RSSUpdateType:
7979
return RSSUpdateType.RSS_CORRECTION
8080
else:
8181
return RSSUpdateType.RSS_UNKNOWN
82-
83-
@staticmethod
84-
def en_page_exists(url: str) -> bool:
85-
"""
86-
Converts a branch URL into an EN URL of the same page and checks if it exists on EN
87-
"""
88-
try:
89-
parsed_url = parse.urlparse(url)
90-
except ValueError:
91-
error(f'Cannot parse URL "{url}"')
92-
return False
93-
parsed_url = parsed_url._replace(scheme='https')._replace(netloc='scp-wiki.wikidot.com')
94-
wl_parsed_url = parsed_url._replace(scheme='https')._replace(netloc='wanderers-library.wikidot.com')
95-
en_url = parse.urlunparse(parsed_url)
96-
wl_url = parse.urlunparse(wl_parsed_url)
97-
try:
98-
head_result = requests.head(en_url, headers={'User-Agent': USER_AGENT})
99-
except requests.RequestException as e:
100-
error(f'Request to {en_url} failed ({str(e)})')
101-
return False
102-
#TODO: Refactor this
103-
match head_result.status_code:
104-
case 200:
105-
return True
106-
case 404:
107-
head_result = requests.head(wl_url, headers={'User-Agent': USER_AGENT})
108-
match head_result.status_code:
109-
case 200:
110-
return True
111-
case 404:
112-
return False
113-
case _:
114-
warning(f'Got unusual status code ({head_result.status_code}) for URL {en_url}')
115-
return False
116-
case _:
117-
warning(f'Got unusual status code ({head_result.status_code}) for URL {en_url}')
118-
return False
11982

12083
def get_rss_update_author(self, update: dict) -> Optional[User]:
12184
update_description = update['description']
@@ -146,17 +109,11 @@ def _process_new_page(self, update) -> bool:
146109
info('Ignoring {title} in RSS feed (couldn\'t match wikidot username {author} to a user)')
147110
return False
148111
debug(f'Check {title} with ts {timestamp}, last db update was {self.__dbs.lastupdated}')
149-
#if title.lower().endswith(IGNORE_BRANCH_TAG):
150-
# info(f'Ignoring {title} in RSS feed (not a translation)')
151-
# return False
152112

153113
if timestamp+TIMEZONE_UTC_OFFSET > self.__dbs.lastupdated:
154114
if self.__dbs.get_article_by_link(update['link']):
155115
info(f'Ignoring {title} in RSS feed (added manually)')
156116
return False
157-
#if not RSSMonitor.en_page_exists(update['link']):
158-
# info(f'Ignoring {title} in RSS feed (EN Wiki page doesn\'t exist)')
159-
# return False
160117
self.__updates.append(RSSUpdate(timestamp+TIMEZONE_UTC_OFFSET, update['link'], title, author, uuid4(), RSSUpdateType.RSS_NEWPAGE))
161118
return True
162119
return False
@@ -172,7 +129,6 @@ def _process_correction(self, update) -> bool:
172129
translation = self.__dbs.get_article_by_link(update['link'])
173130
if not translation:
174131
self.__updates.append(RSSUpdate(timestamp+TIMEZONE_UTC_OFFSET, update['link'], real_title, author, uuid4(), RSSUpdateType.RSS_CORRECTION))
175-
#self.__webhook.send_text(f'Korekci od {author.nickname} pro {real_title} nelze přiřadit k článku. Zapište manuálně.')
176132
warning(f"Correction for {real_title} by {author.nickname} cannot be assigned to an article")
177133
else:
178134
self.__dbs.assign_corrector(translation, author)
@@ -194,8 +150,6 @@ def _process_update(self, update) -> bool:
194150
case RSSUpdateType.RSS_CORRECTION:
195151
self._process_correction(update)
196152

197-
198-
199153
return False
200154

201155
def check(self):

db.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,14 @@ def get_global_stats(self) -> StatisticsRow:
340340
query = "SELECT * FROM Statistics"
341341
data = self.__tryexec(query).fetchone()
342342
return StatisticsRow(*data)
343+
344+
def get_last_article(self, uid: int, original: bool = False) -> t.Optional[Article]:
345+
query = "SELECT * FROM Article WHERE idauthor=? AND is_original=? ORDER BY added DESC, id DESC LIMIT 1"
346+
data = (uid, original)
347+
row = self.__tryexec(query, data).fetchone()
348+
if not row:
349+
return None
350+
return self.__make_article(row)
343351

344352
def add_article(self, a: Article) -> int:
345353
query = "INSERT INTO Article (name, words, bonus, added, link, idauthor, is_original) VALUES (?, ?, ?, ?, ?, ?, ?)"

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ hypercorn==0.14.4
2121
hyperframe==6.0.1
2222
idna==3.4
2323
itsdangerous==2.1.2
24-
Jinja2==3.1.4
24+
Jinja2==3.1.5
2525
lxml==4.9.3
2626
MarkupSafe==2.1.3
2727
MechanicalSoup==1.3.0
@@ -40,7 +40,7 @@ tomli==2.0.1
4040
tzdata==2023.3
4141
tzlocal==5.0.1
4242
urllib3==2.2.2
43-
waitress==2.1.2
4443
Werkzeug==3.0.6
44+
waitress==3.0.1
4545
wsproto==1.2.0
4646
WTForms==3.0.1

0 commit comments

Comments
 (0)