Skip to content

Commit 2eda2a7

Browse files
committed
feat: Add 404.html for browers
1 parent 2fe9533 commit 2eda2a7

File tree

3 files changed

+322
-25
lines changed

3 files changed

+322
-25
lines changed

src/paste/main.py

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,45 @@
1+
import json
2+
import os
3+
import shutil
4+
from pathlib import Path
5+
from typing import Any, Awaitable, Callable, List, Optional, Union
6+
17
from fastapi import (
8+
FastAPI,
29
File,
3-
UploadFile,
4-
HTTPException,
5-
status,
6-
Request,
710
Form,
8-
FastAPI,
911
Header,
12+
HTTPException,
13+
Request,
1014
Response,
15+
UploadFile,
16+
status,
1117
)
18+
from fastapi.middleware.cors import CORSMiddleware
1219
from fastapi.responses import (
13-
PlainTextResponse,
1420
HTMLResponse,
15-
RedirectResponse,
1621
JSONResponse,
22+
PlainTextResponse,
23+
RedirectResponse,
1724
)
18-
from starlette.requests import Request
19-
from starlette.responses import Response
20-
from typing import Callable, Awaitable, List, Optional, Union, Any
21-
import shutil
22-
import os
23-
import json
24-
from pathlib import Path
2525
from fastapi.templating import Jinja2Templates
26-
from fastapi.middleware.cors import CORSMiddleware
27-
from slowapi.errors import RateLimitExceeded
28-
from slowapi import Limiter, _rate_limit_exceeded_handler
29-
from slowapi.util import get_remote_address
30-
from .utils import generate_uuid, _find_without_extension
31-
from .middleware import LimitUploadSize
3226
from pygments import highlight
33-
from pygments.lexers import get_lexer_by_name, guess_lexer
3427
from pygments.formatters import HtmlFormatter
28+
from pygments.lexers import get_lexer_by_name, guess_lexer
3529
from pygments.util import ClassNotFound
36-
from . import __version__, __author__, __contact__, __url__
37-
from .schema import PasteCreate, PasteResponse, PasteDetails
30+
from slowapi import Limiter, _rate_limit_exceeded_handler
31+
from slowapi.errors import RateLimitExceeded
32+
from slowapi.util import get_remote_address
33+
from starlette.requests import Request
34+
from starlette.responses import Response
35+
from starlette.exceptions import HTTPException as StarletteHTTPException
36+
from fastapi.exception_handlers import http_exception_handler
37+
38+
from . import __author__, __contact__, __url__, __version__
39+
from .middleware import LimitUploadSize
40+
from .schema import PasteCreate, PasteDetails, PasteResponse
41+
from .utils import _find_without_extension, generate_uuid
42+
from .config import get_settings
3843

3944
DESCRIPTION: str = "paste.py 🐍 - A pastebin written in python."
4045

@@ -64,9 +69,37 @@ def rate_limit_exceeded_handler(
6469

6570
app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
6671

72+
73+
@app.exception_handler(StarletteHTTPException)
74+
async def custom_http_exception_handler(
75+
request: Request, exc: StarletteHTTPException
76+
) -> Response:
77+
"""
78+
Custom exception handler for HTTP exceptions.
79+
"""
80+
if exc.status_code == 404:
81+
user_agent = request.headers.get("user-agent", "")
82+
is_browser_request = "Mozilla" in user_agent
83+
84+
if is_browser_request:
85+
try:
86+
return templates.TemplateResponse(
87+
"404.html", {"request": request}, status_code=404
88+
)
89+
except Exception as e:
90+
print(f"Template error: {e}") # For debugging
91+
return PlainTextResponse("404: Template Error", status_code=404)
92+
else:
93+
return PlainTextResponse(
94+
"404: The requested resource was not found", status_code=404
95+
)
96+
97+
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
98+
99+
67100
origins: List[str] = ["*"]
68101

69-
BASE_URL: str = r"http://paste.fosscu.org"
102+
BASE_URL: str = get_settings().BASE_URL
70103
app.add_middleware(
71104
CORSMiddleware,
72105
allow_origins=origins,

src/paste/templates/404.html

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
{% extends 'base.html' %}
2+
3+
{% block title %} 404 - Page Not Found | paste.py 🐍 {% endblock %}
4+
5+
{% block headlinks %}
6+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
7+
{% endblock %}
8+
9+
{% block style %}
10+
11+
@import url('https://fonts.cdnfonts.com/css/vt323');
12+
@import url('https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/fira_code.min.css');
13+
14+
:root {
15+
--terminal-green: #00ff00;
16+
--terminal-dark: #0c0c0c;
17+
--terminal-shadow: rgba(0, 255, 0, 0.2);
18+
--terminal-font: 'VT323', 'Fira Code', monospace;
19+
}
20+
21+
* {
22+
box-sizing: border-box;
23+
margin: 0;
24+
padding: 0;
25+
}
26+
27+
body {
28+
background-color: var(--terminal-dark);
29+
margin: 0;
30+
padding: 20px;
31+
font-family: var(--terminal-font);
32+
line-height: 1.6;
33+
font-size: 20px;
34+
color: var(--terminal-green);
35+
height: 100vh;
36+
display: flex;
37+
justify-content: center;
38+
align-items: center;
39+
overflow: hidden;
40+
}
41+
42+
#matrix-bg {
43+
position: fixed;
44+
top: 0;
45+
left: 0;
46+
width: 100vw;
47+
height: 100vh;
48+
z-index: -1;
49+
opacity: 0.15;
50+
}
51+
52+
.container {
53+
width: 90%;
54+
max-width: 800px;
55+
background-color: rgba(12, 12, 12, 0.95);
56+
border: 1px solid var(--terminal-green);
57+
box-shadow: 0 0 20px var(--terminal-shadow);
58+
position: relative;
59+
backdrop-filter: blur(5px);
60+
}
61+
62+
.terminal-header {
63+
background: var(--terminal-green);
64+
color: var(--terminal-dark);
65+
padding: 12px;
66+
font-size: 22px;
67+
display: flex;
68+
justify-content: space-between;
69+
align-items: center;
70+
}
71+
72+
.controls {
73+
display: flex;
74+
gap: 8px;
75+
}
76+
77+
.control {
78+
width: 14px;
79+
height: 14px;
80+
border-radius: 50%;
81+
border: 1px solid var(--terminal-dark);
82+
}
83+
84+
.control.close { background: #ff5f56; }
85+
.control.minimize { background: #ffbd2e; }
86+
.control.maximize { background: #27c93f; }
87+
88+
.terminal-body {
89+
padding: 30px;
90+
min-height: 300px;
91+
}
92+
93+
.error-code {
94+
font-size: 72px;
95+
margin-bottom: 20px;
96+
text-shadow: 0 0 10px var(--terminal-shadow);
97+
animation: glitch 1s infinite;
98+
}
99+
100+
.cursor {
101+
display: inline-block;
102+
width: 10px;
103+
height: 24px;
104+
background-color: var(--terminal-green);
105+
margin-left: 5px;
106+
animation: blink 1s infinite;
107+
}
108+
109+
.command-line {
110+
margin: 20px 0;
111+
display: flex;
112+
align-items: center;
113+
}
114+
115+
.prompt {
116+
color: var(--terminal-green);
117+
margin-right: 10px;
118+
}
119+
120+
.message {
121+
margin: 20px 0;
122+
line-height: 1.6;
123+
}
124+
125+
.action-link {
126+
display: inline-block;
127+
margin-top: 20px;
128+
color: var(--terminal-green);
129+
text-decoration: none;
130+
border: 1px solid var(--terminal-green);
131+
padding: 10px 20px;
132+
transition: all 0.3s ease;
133+
}
134+
135+
.action-link:hover {
136+
background-color: var(--terminal-green);
137+
color: var(--terminal-dark);
138+
box-shadow: 0 0 15px var(--terminal-shadow);
139+
}
140+
141+
@keyframes blink {
142+
0%, 100% { opacity: 1; }
143+
50% { opacity: 0; }
144+
}
145+
146+
@keyframes glitch {
147+
0% { text-shadow: 0 0 10px var(--terminal-shadow); }
148+
25% { text-shadow: -2px 0 #ff00ff, 2px 0 #00ffff; }
149+
50% { text-shadow: 0 0 10px var(--terminal-shadow); }
150+
75% { text-shadow: 2px 0 #ff00ff, -2px 0 #00ffff; }
151+
100% { text-shadow: 0 0 10px var(--terminal-shadow); }
152+
}
153+
154+
@media only screen and (max-width: 768px) {
155+
body {
156+
padding: 10px;
157+
font-size: 16px;
158+
}
159+
160+
.container {
161+
width: 95%;
162+
}
163+
164+
.error-code {
165+
font-size: 48px;
166+
}
167+
168+
.terminal-body {
169+
padding: 20px;
170+
}
171+
}
172+
173+
{% endblock %}
174+
175+
{% block content %}
176+
177+
<canvas id="matrix-bg"></canvas>
178+
<div class="container">
179+
<div class="terminal-header">
180+
<div class="controls">
181+
<div class="control close"></div>
182+
<div class="control minimize"></div>
183+
<div class="control maximize"></div>
184+
</div>
185+
<a href="https://paste.fosscu.org" class="header-link">
186+
<div style="color: var(--terminal-dark);"><u>paste.py</u> 🐍</div>
187+
</a>
188+
<div style="width: 50px;"></div>
189+
</div>
190+
<div class="terminal-body">
191+
<div class="error-code">Error 404</div>
192+
<div class="command-line">
193+
<span class="prompt">$</span>
194+
<span class="typed-text">locate requested_page</span>
195+
<span class="cursor"></span>
196+
</div>
197+
<div class="message">
198+
<p>> Page not found in filesystem</p>
199+
<p>> The requested resource could not be located on this server</p>
200+
<p>> Running diagnostic...</p>
201+
<p>> Suggestion: Return to home directory</p>
202+
</div>
203+
<a href="/" class="action-link">$ cd /home</a>
204+
</div>
205+
</div>
206+
207+
208+
{% endblock %}
209+
210+
{% block script %}
211+
212+
document.addEventListener('DOMContentLoaded', function() {
213+
const canvas = document.getElementById('matrix-bg');
214+
const ctx = canvas.getContext('2d');
215+
216+
canvas.width = window.innerWidth;
217+
canvas.height = window.innerHeight;
218+
219+
const characters = "ヲアウエオカキケコサシスセソタツテナニヌネハヒホマミムメモヤユラリワ0123456789".split("");
220+
const fontSize = 16;
221+
const columns = canvas.width / fontSize;
222+
const drops = [];
223+
224+
for(let i = 0; i < columns; i++) {
225+
drops[i] = 1;
226+
}
227+
228+
function draw() {
229+
ctx.fillStyle = "rgba(0, 0, 0, 0.05)";
230+
ctx.fillRect(0, 0, canvas.width, canvas.height);
231+
232+
ctx.fillStyle = "#0F0";
233+
ctx.font = fontSize + "px monospace";
234+
235+
for(let i = 0; i < drops.length; i++) {
236+
const text = characters[Math.floor(Math.random() * characters.length)];
237+
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
238+
239+
if(drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
240+
drops[i] = 0;
241+
}
242+
243+
drops[i]++;
244+
}
245+
}
246+
247+
window.addEventListener('resize', () => {
248+
canvas.width = window.innerWidth;
249+
canvas.height = window.innerHeight;
250+
});
251+
252+
setInterval(draw, 35);
253+
254+
// Boot sequence effect
255+
const container = document.querySelector('.container');
256+
container.style.opacity = '0';
257+
258+
setTimeout(() => {
259+
container.style.transition = 'opacity 0.5s';
260+
container.style.opacity = '1';
261+
}, 300);
262+
});
263+
264+
{% endblock %}

src/paste/templates/base.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
<html lang="en">
33
<head>
44
<title> {% block title %} {% endblock %} </title>
5-
{% block headlinks %} {% endblock %}
65
<meta charset="UTF-8"/>
76
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
87
<meta name="og:title" content="paste.py 🐍"/>
@@ -14,6 +13,7 @@
1413
<meta name="og:type" content="website"/>
1514
<meta name="og:url" content="https://paste.fosscu.org"/>
1615
<meta name="og:locale" content="en_US"/>
16+
{% block headlinks %} {% endblock %}
1717
<style>
1818
{% block style %}
1919
{% endblock %}

0 commit comments

Comments
 (0)