2023-07-31 18:04:53 -07:00
|
|
|
from collections import defaultdict
|
|
|
|
|
import functools
|
|
|
|
|
import json
|
2023-08-06 00:17:45 -07:00
|
|
|
from typing import Any, Mapping, MutableMapping, Optional, Sequence
|
|
|
|
|
import urllib.parse
|
2023-07-31 18:04:53 -07:00
|
|
|
|
|
|
|
|
from aiohttp import web
|
|
|
|
|
from jinja2 import Environment, PackageLoader, select_autoescape
|
|
|
|
|
|
2023-08-02 18:31:36 -07:00
|
|
|
from . import config
|
2023-07-31 18:04:53 -07:00
|
|
|
from .db import get_db, search_db
|
|
|
|
|
|
2023-08-02 18:38:26 -07:00
|
|
|
|
2023-07-31 18:04:53 -07:00
|
|
|
# 2023-07-24 - eating stirfry tonight. It's pretty bad.
|
|
|
|
|
# 2023-07-31 - In a meeting. Hope I don't get caught
|
|
|
|
|
|
2023-08-02 18:31:36 -07:00
|
|
|
|
2023-08-06 00:17:45 -07:00
|
|
|
def route_url(url: str, args: Optional[Mapping[str, Any]] = None):
|
|
|
|
|
if args is None:
|
|
|
|
|
args = dict()
|
2023-08-02 20:16:47 -07:00
|
|
|
assert url == "" or url[0] == "/", "URL must be blank or start with a slash"
|
|
|
|
|
if url in ("", "/"):
|
2023-08-06 00:17:45 -07:00
|
|
|
base = config.HTTP_ROOT
|
2023-08-02 18:31:36 -07:00
|
|
|
else:
|
2023-08-06 00:17:45 -07:00
|
|
|
base = f"{config.HTTP_ROOT}{url}"
|
|
|
|
|
|
|
|
|
|
if args:
|
|
|
|
|
return base + "?" + urllib.parse.urlencode(args)
|
|
|
|
|
else:
|
|
|
|
|
return base
|
2023-08-02 20:16:47 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def static_url(resource: str):
|
|
|
|
|
return f"{config.STATIC_ROOT}/{resource}"
|
2023-08-02 18:31:36 -07:00
|
|
|
|
|
|
|
|
|
2023-07-31 18:04:53 -07:00
|
|
|
_env = Environment(
|
|
|
|
|
loader=PackageLoader("chanbans"),
|
|
|
|
|
autoescape=select_autoescape(),
|
|
|
|
|
)
|
2023-08-02 18:31:36 -07:00
|
|
|
_env.globals.update(
|
|
|
|
|
{
|
|
|
|
|
"route_url": route_url,
|
2023-08-02 20:16:47 -07:00
|
|
|
"static_url": static_url,
|
2023-08-02 18:31:36 -07:00
|
|
|
}
|
|
|
|
|
)
|
2023-07-31 18:04:53 -07:00
|
|
|
|
|
|
|
|
TemplateContext = MutableMapping[str, Any]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HtmlResponse(web.Response):
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
headers = kwargs.get(
|
|
|
|
|
"headers",
|
|
|
|
|
{
|
|
|
|
|
"Content-Type": "text/html",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
kwargs["headers"] = headers
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TemplateView(web.View):
|
|
|
|
|
template_path: str
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
assert not hasattr(self, "_env")
|
|
|
|
|
|
|
|
|
|
global _env
|
|
|
|
|
self._env = _env
|
|
|
|
|
|
|
|
|
|
self.template = self.env.get_template(self.template_path)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def env(self) -> Environment:
|
|
|
|
|
return self._env
|
|
|
|
|
|
|
|
|
|
def get_context(self) -> TemplateContext:
|
|
|
|
|
"Gets the context for this template"
|
2023-08-02 18:31:36 -07:00
|
|
|
return {
|
|
|
|
|
"config": config,
|
|
|
|
|
}
|
2023-07-31 18:04:53 -07:00
|
|
|
|
|
|
|
|
def template_render(self) -> str:
|
|
|
|
|
"Renders the template on this object."
|
|
|
|
|
context = self.get_context()
|
|
|
|
|
return self.template.render(**context)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
|
# Template views
|
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def template_view_factory(path: str) -> type:
|
|
|
|
|
class FactoryView(TemplateView):
|
|
|
|
|
template_path = path
|
|
|
|
|
|
|
|
|
|
async def get(self):
|
|
|
|
|
html = self.template_render()
|
|
|
|
|
return HtmlResponse(text=html)
|
|
|
|
|
|
|
|
|
|
return FactoryView
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IndexView(TemplateView):
|
|
|
|
|
template_path = "index.html"
|
|
|
|
|
|
|
|
|
|
@functools.cache
|
|
|
|
|
def get_context(self) -> TemplateContext:
|
|
|
|
|
ctx = super().get_context()
|
|
|
|
|
query = self.get_search_query()
|
|
|
|
|
ctx["posts"] = search_db(**query)
|
|
|
|
|
ctx["query"] = query
|
2023-08-06 00:17:45 -07:00
|
|
|
ctx["next_page"] = route_url("/", {**query, "offset": query["offset"] + 100})
|
|
|
|
|
ctx["prev_page"] = route_url("/", {**query, "offset": query["offset"] - 100})
|
2023-07-31 18:04:53 -07:00
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
# Query parameters:
|
|
|
|
|
# ?board=pol
|
|
|
|
|
# &reason=evasion
|
|
|
|
|
# &name=Anonymous
|
|
|
|
|
# &trip=asdfbgh
|
|
|
|
|
# &com=comment%20search
|
|
|
|
|
# &sub=subject%20search
|
|
|
|
|
# &time_before=123456
|
|
|
|
|
# &time_after=123456
|
|
|
|
|
# &md5=md5_sum
|
2023-08-06 00:17:45 -07:00
|
|
|
# &offset=0
|
2023-07-31 18:04:53 -07:00
|
|
|
|
|
|
|
|
def get_search_query(self):
|
|
|
|
|
allowed_keys = (
|
|
|
|
|
"board",
|
|
|
|
|
"reason",
|
|
|
|
|
"name",
|
|
|
|
|
"trip",
|
|
|
|
|
"com",
|
|
|
|
|
"sub",
|
|
|
|
|
"time_before",
|
|
|
|
|
"time_after",
|
|
|
|
|
"md5",
|
2023-08-06 00:17:45 -07:00
|
|
|
"offset",
|
2023-07-31 18:04:53 -07:00
|
|
|
)
|
|
|
|
|
query = {
|
|
|
|
|
key: value
|
|
|
|
|
for key, value in self.request.query.items()
|
|
|
|
|
if key in allowed_keys and key in self.request.query
|
|
|
|
|
}
|
2023-08-06 00:17:45 -07:00
|
|
|
if "offset" in query:
|
|
|
|
|
try:
|
|
|
|
|
query["offset"] = int(query["offset"])
|
|
|
|
|
except ValueError:
|
|
|
|
|
query["offset"] = 0
|
|
|
|
|
else:
|
|
|
|
|
query["offset"] = 0
|
|
|
|
|
|
2023-07-31 18:04:53 -07:00
|
|
|
if "time_before" in query:
|
|
|
|
|
try:
|
|
|
|
|
query["time_before"] = int(query["time_before"])
|
|
|
|
|
except ValueError:
|
|
|
|
|
query.pop("time_before")
|
|
|
|
|
if "time_after" in query:
|
|
|
|
|
try:
|
|
|
|
|
query["time_after"] = int(query["time_after"])
|
|
|
|
|
except ValueError:
|
|
|
|
|
query.pop("time_after")
|
|
|
|
|
return query
|
|
|
|
|
|
|
|
|
|
async def get(self):
|
|
|
|
|
html = self.template_render()
|
|
|
|
|
return HtmlResponse(text=html)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
|
# Routes
|
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = web.Application()
|
|
|
|
|
app.add_routes(
|
|
|
|
|
[
|
2023-08-02 20:16:47 -07:00
|
|
|
web.get(config.HTTP_ROOT, IndexView),
|
|
|
|
|
web.get(f"{config.HTTP_ROOT}/faq", template_view_factory("faq.html")),
|
|
|
|
|
web.get(f"{config.HTTP_ROOT}/news", template_view_factory("news.html")),
|
2023-07-31 18:04:53 -07:00
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
2023-08-02 20:16:47 -07:00
|
|
|
if config.STATIC_HANDLER == "local":
|
|
|
|
|
app.add_routes(
|
|
|
|
|
[
|
|
|
|
|
web.static(
|
|
|
|
|
config.STATIC_ROOT,
|
|
|
|
|
config.STATIC_LOCAL_PATH,
|
|
|
|
|
follow_symlinks=config.STATIC_LOCAL_FOLLOW_SYMLINKS,
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
2023-08-02 18:31:36 -07:00
|
|
|
|
2023-07-31 18:04:53 -07:00
|
|
|
def run_app():
|
|
|
|
|
web.run_app(app)
|