-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathapp.py
More file actions
executable file
·228 lines (186 loc) · 6.53 KB
/
Copy pathapp.py
File metadata and controls
executable file
·228 lines (186 loc) · 6.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#! /usr/bin/env python
import functools
import hashlib
import json
import os
import time
import traceback
import urllib.parse
from typing import Any
from earwigbot.wiki.copyvios import globalize
from flask import make_response, redirect, render_template, request, session
from werkzeug import Response
from werkzeug.exceptions import HTTPException
from copyvios import app
from copyvios.api import format_api_error, handle_api_request
from copyvios.attribution import get_attribution_info
from copyvios.auth import clear_login_session, oauth_login_end, oauth_login_start
from copyvios.background import get_background
from copyvios.cache import cache
from copyvios.checker import (
T_POSSIBLE,
T_SUSPECT,
CopyvioCheckError,
ErrorCode,
do_check,
)
from copyvios.cookies import get_cookies, get_new_cookies
from copyvios.highlighter import highlight_delta
from copyvios.misc import get_notice, get_permalink
from copyvios.query import CheckQuery
from copyvios.settings import process_settings
from copyvios.sites import update_sites
AnyResponse = Response | str | bytes
app.logger.info(f"Flask server started {time.asctime()}")
globalize(num_workers=8)
@app.errorhandler(Exception)
def handle_errors(exc: Exception) -> AnyResponse | HTTPException:
if isinstance(exc, HTTPException):
return exc
if app.debug:
raise # Use built-in debugger
app.logger.exception("Caught exception:")
error_page = render_template("error.html.jinja", traceback=traceback.format_exc())
return make_response(error_page, 500)
@app.context_processor
def setup_context() -> dict[str, Any]:
return {
"T_POSSIBLE": T_POSSIBLE,
"T_SUSPECT": T_SUSPECT,
"ErrorCode": ErrorCode,
"cache": cache,
"dump_json": json.dumps,
"get_attribution_info": get_attribution_info,
"get_background": get_background,
"get_cookies": get_cookies,
"get_notice": get_notice,
"get_permalink": get_permalink,
"highlight_delta": highlight_delta,
}
@app.after_request
def add_new_cookies(response: Response) -> Response:
for cookie in get_new_cookies():
response.headers.add("Set-Cookie", cookie)
return response
@app.after_request
def write_access_log(response: Response) -> Response:
app.logger.debug(
f"{time.asctime()} {request.method} {request.path} "
f"{request.values.to_dict()} -> {response.status_code}"
)
return response
@functools.lru_cache
def _get_hash(path: str, mtime: float) -> str:
del mtime # mtime is used as part of the cache key
with open(path, "rb") as fp:
return hashlib.sha1(fp.read()).hexdigest()
def external_url_handler(
error: Exception, endpoint: str, values: dict[str, Any]
) -> str:
if endpoint == "static" and "file" in values:
assert app.static_folder is not None
path = os.path.join(app.static_folder, values["file"])
mtime = os.path.getmtime(path)
hashstr = _get_hash(path, mtime)
return f"/static/{values['file']}?v={hashstr}"
raise error
app.url_build_error_handlers.append(external_url_handler)
@app.route("/")
def index() -> AnyResponse:
update_sites()
query = CheckQuery.from_get_args()
try:
result = do_check(query)
error = None
except CopyvioCheckError as exc:
if exc.code == ErrorCode.NOT_LOGGED_IN:
target = "/login?next=" + urllib.parse.quote_plus(
"/?" + request.query_string.decode(errors="ignore")
)
return redirect(target, 302)
app.logger.exception(f"Copyvio check failed on {query}")
result = None
error = exc
return render_template(
"index.html.jinja",
query=query,
result=result,
error=error,
splash=not result,
)
@app.route("/login", methods=["GET", "POST"])
def login() -> AnyResponse:
try:
redirect_url = oauth_login_start() if request.method == "POST" else None
if redirect_url:
return redirect(redirect_url, 302)
except Exception as e:
app.logger.exception("OAuth login start failed")
error_str = str(e)
else:
if session.get("username") is not None:
return redirect("/", 302)
error_str = request.args.get("error")
is_search = False
if request.args.get("next", "").startswith("/?"):
next_query = dict(urllib.parse.parse_qsl(request.args["next"]))
if next_query.get("action") == "search":
is_search = True
return render_template("login.html.jinja", error=error_str, is_search=is_search)
@app.route("/logout", methods=["GET", "POST"])
def logout() -> AnyResponse:
if request.method == "POST":
clear_login_session()
return redirect("/", 302)
else:
return render_template("logout.html.jinja")
@app.route("/oauth-callback")
def oauth_callback() -> AnyResponse:
try:
next_url = oauth_login_end()
except Exception as e:
app.logger.exception("OAuth login end failed")
return redirect("/login?error=" + urllib.parse.quote_plus(str(e)), 302)
else:
return redirect(next_url, 302)
@app.route("/settings", methods=["GET", "POST"])
def settings() -> AnyResponse:
status = process_settings() if request.method == "POST" else None
update_sites()
return render_template(
"settings.html.jinja",
status=status,
default_site=cache.bot.wiki.get_site(),
splash=True,
)
@app.route("/api")
def api() -> AnyResponse:
return render_template("api_help.html.jinja")
@app.route("/api.json")
def api_json() -> AnyResponse:
if not request.args:
return render_template("api_help.html.jinja")
format = request.args.get("format", "json")
if format in ["json", "jsonfm"]:
update_sites()
try:
result = handle_api_request()
except Exception as exc:
app.logger.exception("API request failed")
result = format_api_error("unhandled_exception", exc)
else:
errmsg = f"Unknown format: {format!r}"
result = format_api_error("unknown_format", errmsg)
if format == "jsonfm":
return render_template("api_result.html.jinja", result=result)
resp = make_response(json.dumps(result))
resp.mimetype = "application/json"
resp.headers["Access-Control-Allow-Origin"] = "*"
return resp
if app.debug:
# Silence browser 404s when testing
@app.route("/favicon.ico")
def favicon() -> AnyResponse:
return app.send_static_file("favicon.ico")
if __name__ == "__main__":
app.run()