http: Add base HTTP server implementation.
* Add HTTP server base implementation with some basic pages * Add Jinja2 dependency * Things are mostly working, except for static resources. Those will have to come next. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -3,11 +3,16 @@ import argparse
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .pull import pull
|
from .pull import pull
|
||||||
|
from .http import run_app
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
parser = argparse.ArgumentParser(description="Run 4chan bans archiver")
|
parser = argparse.ArgumentParser(description="Run 4chan bans archiver")
|
||||||
parser.add_argument('--log-level', choices=['debug', 'info', 'warning', 'error', 'critical'], default='info')
|
parser.add_argument(
|
||||||
|
"--log-level",
|
||||||
|
choices=["debug", "info", "warning", "error", "critical"],
|
||||||
|
default="info",
|
||||||
|
)
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(title="Commands", dest="command")
|
subparsers = parser.add_subparsers(title="Commands", dest="command")
|
||||||
|
|
||||||
@@ -36,7 +41,7 @@ def parse_args():
|
|||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
try:
|
try:
|
||||||
level = getattr(logging, args.log_level.upper())
|
level = getattr(logging, args.log_level.upper())
|
||||||
@@ -44,13 +49,18 @@ async def main():
|
|||||||
print(f"ERROR: no such logging level {args.log_level}. Exiting")
|
print(f"ERROR: no such logging level {args.log_level}. Exiting")
|
||||||
raise SystemExit()
|
raise SystemExit()
|
||||||
|
|
||||||
logging.basicConfig(level=level, format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
|
logging.basicConfig(
|
||||||
|
level=level, format="%(asctime)s %(name)-12s %(levelname)-8s %(message)s"
|
||||||
|
)
|
||||||
match args.command:
|
match args.command:
|
||||||
case "pull":
|
case "pull":
|
||||||
await pull()
|
asyncio.run(pull())
|
||||||
case "serve":
|
case "serve":
|
||||||
print("TODO: HTTP server")
|
run_app()
|
||||||
case command:
|
case command:
|
||||||
assert False, f"unknown command {command} that was not caught in add_subcommand"
|
assert (
|
||||||
|
False
|
||||||
|
), f"unknown command {command} that was not caught in add_subcommand"
|
||||||
|
|
||||||
asyncio.run(main())
|
|
||||||
|
main()
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ def search_db(
|
|||||||
board: str = "",
|
board: str = "",
|
||||||
reason: str = "",
|
reason: str = "",
|
||||||
name: str = "",
|
name: str = "",
|
||||||
|
trip: str = "",
|
||||||
com: str = "",
|
com: str = "",
|
||||||
sub: str = "",
|
sub: str = "",
|
||||||
time_before: int = sys.maxsize,
|
time_before: int = sys.maxsize,
|
||||||
@@ -68,9 +69,10 @@ def search_db(
|
|||||||
select *
|
select *
|
||||||
from bans
|
from bans
|
||||||
where
|
where
|
||||||
(:board is null or board like :board_like)
|
(:board = "" or board = :board)
|
||||||
and (:reason = "" or reason like :reason_like)
|
and (:reason = "" or reason like :reason_like)
|
||||||
and (:name = "" or name like :name_like)
|
and (:name = "" or name like :name_like)
|
||||||
|
and (:trip = "" or trip like :trip_like)
|
||||||
and (:com = "" or com like :com_like)
|
and (:com = "" or com like :com_like)
|
||||||
and (:sub = "" or sub like :sub_like)
|
and (:sub = "" or sub like :sub_like)
|
||||||
and (time <= :time_before)
|
and (time <= :time_before)
|
||||||
@@ -82,11 +84,12 @@ def search_db(
|
|||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
"board": board,
|
"board": board,
|
||||||
"board_like": f"%{board}%",
|
|
||||||
"reason": reason,
|
"reason": reason,
|
||||||
"reason_like": f"%{reason}%",
|
"reason_like": f"%{reason}%",
|
||||||
"name": name,
|
"name": name,
|
||||||
"name_like": f"%{name}%",
|
"name_like": f"%{name}%",
|
||||||
|
"trip": trip,
|
||||||
|
"trip_like": f"%{trip}%",
|
||||||
"com": com,
|
"com": com,
|
||||||
"com_like": f"%{com}%",
|
"com_like": f"%{com}%",
|
||||||
"sub": sub,
|
"sub": sub,
|
||||||
|
|||||||
174
chanbans/http.py
Normal file
174
chanbans/http.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
import functools
|
||||||
|
import json
|
||||||
|
from typing import Any, MutableMapping, Optional, Sequence
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||||
|
|
||||||
|
from .db import get_db, search_db
|
||||||
|
|
||||||
|
# 2023-07-24 - eating stirfry tonight. It's pretty bad.
|
||||||
|
# 2023-07-31 - In a meeting. Hope I don't get caught
|
||||||
|
|
||||||
|
_env = Environment(
|
||||||
|
loader=PackageLoader("chanbans"),
|
||||||
|
autoescape=select_autoescape(),
|
||||||
|
)
|
||||||
|
|
||||||
|
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"
|
||||||
|
return dict()
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
# &page=0
|
||||||
|
|
||||||
|
def get_search_query(self):
|
||||||
|
allowed_keys = (
|
||||||
|
"board",
|
||||||
|
"reason",
|
||||||
|
"name",
|
||||||
|
"trip",
|
||||||
|
"com",
|
||||||
|
"sub",
|
||||||
|
"time_before",
|
||||||
|
"time_after",
|
||||||
|
"md5",
|
||||||
|
"page",
|
||||||
|
)
|
||||||
|
query = {
|
||||||
|
key: value
|
||||||
|
for key, value in self.request.query.items()
|
||||||
|
if key in allowed_keys and key in self.request.query
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# API views
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class BansJson(web.View):
|
||||||
|
async def get(self):
|
||||||
|
db = get_db()
|
||||||
|
page = self.request.match_info["page"]
|
||||||
|
|
||||||
|
# TODO - configure the number of bans per page in settings
|
||||||
|
BANS_PER_PAGE = 10
|
||||||
|
|
||||||
|
result = db.execute(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM bans
|
||||||
|
ORDER BY id
|
||||||
|
DESC LIMIT :limit
|
||||||
|
""",
|
||||||
|
{"limit": BANS_PER_PAGE},
|
||||||
|
)
|
||||||
|
return web.json_response(list(result.fetchall()))
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Routes
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
app = web.Application()
|
||||||
|
app.add_routes(
|
||||||
|
[
|
||||||
|
web.get(r"/bans", IndexView),
|
||||||
|
web.get(r"/bans/faq", template_view_factory("faq.html")),
|
||||||
|
#web.get(r"/api/v1/bans/{page:\d+}", BansJson),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def run_app():
|
||||||
|
web.run_app(app)
|
||||||
179
chanbans/templates/base.html
Normal file
179
chanbans/templates/base.html
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{% block head %}
|
||||||
|
{% endblock head %}
|
||||||
|
<title>
|
||||||
|
{% block title %}
|
||||||
|
{% endblock title %}
|
||||||
|
</title>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #ffe linear-gradient(180deg, rgba(254,214,175,1) 0%, rgba(255,255,238,1) 200px) repeat-x;
|
||||||
|
font-family:helvetica neue,arial,sans-serif;
|
||||||
|
margin:5px 0;
|
||||||
|
padding:0 5px;
|
||||||
|
font-size:13px;
|
||||||
|
color: maroon;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
margin: 5px 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search {
|
||||||
|
margin: auto;
|
||||||
|
width: 300px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bans-table {
|
||||||
|
border-top: 1px solid maroon;
|
||||||
|
border-left: 1px solid maroon;
|
||||||
|
border-right: 1px solid maroon;
|
||||||
|
vertical-align: top;
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
marin-top: 30px;
|
||||||
|
background-color: #fff;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bans-table td {
|
||||||
|
border-left: 1px solid maroon;
|
||||||
|
padding: 5px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bans-table th {
|
||||||
|
width: auto;
|
||||||
|
border-left: 1px solid maroon;
|
||||||
|
background-color: #fca;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 2px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bans-table tr {
|
||||||
|
border-bottom: 1px solid maroon;
|
||||||
|
}
|
||||||
|
|
||||||
|
.constructed-post td:nth-child(1) {
|
||||||
|
word-break: normal;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subject {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws.reply {
|
||||||
|
background-color: #d6daf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nws.reply {
|
||||||
|
color: maroon;
|
||||||
|
background-color: #f0e0d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws .subject {
|
||||||
|
color: #0f0c5d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nws .subject {
|
||||||
|
color: #cc1105;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: #117743;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poster-trip {
|
||||||
|
color: #117743;
|
||||||
|
font-weight: 400 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-thumb {
|
||||||
|
margin: 3px 20px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-thumb img {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-message {
|
||||||
|
margin: 13px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote {
|
||||||
|
color: #789922;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% block style %}
|
||||||
|
{% endblock style %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block body %}
|
||||||
|
<header>
|
||||||
|
{% block header %}
|
||||||
|
<div id="header">
|
||||||
|
<h1>4chan bans archive</h1>
|
||||||
|
<p>
|
||||||
|
All data is retrieved from <a href="https://www.4chan.org/bans">https://www.4chan.org/bans</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This is not a comprehensive list of all bans - only what the moderation team makes public.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endblock header %}
|
||||||
|
</header>
|
||||||
|
<nav>
|
||||||
|
[ <a href="/bans">Home</a> / <a href="/bans/faq">FAQ</a> ]
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
{% block main %}
|
||||||
|
{% endblock main %}
|
||||||
|
</main>
|
||||||
|
{% endblock body %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
38
chanbans/templates/faq.html
Normal file
38
chanbans/templates/faq.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}4chan bans archive FAQ - hiddenservice.cc{% endblock title %}
|
||||||
|
|
||||||
|
{% block style %}
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.infobox {
|
||||||
|
background-color: #fff;
|
||||||
|
width: 33%;
|
||||||
|
margin: auto;
|
||||||
|
padding: 5px;
|
||||||
|
bottom: 0;
|
||||||
|
border: 1px solid maroon;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
{% endblock style %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<div class="infobox">
|
||||||
|
<h4>What is this place?</h4>
|
||||||
|
<p>
|
||||||
|
<a href="https://www.4chan.org">4chan</a> will periodically (every 15-60
|
||||||
|
minutes) publish a small list of bans that have been handed out on the
|
||||||
|
website for transparency. This website exists to archive those bans and
|
||||||
|
uphold that transparency.
|
||||||
|
</p>
|
||||||
|
<h4>What happened to 4bans?</h4>
|
||||||
|
<p>
|
||||||
|
4bans is still around. I'm not affiliated with them. Their DNS provider was
|
||||||
|
having "major issues" - I don't know the full situation. They are still
|
||||||
|
accessible through their IP address directly:
|
||||||
|
<a href="http://185.10.68.107:1776">http://185.10.68.107:1776</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
69
chanbans/templates/index.html
Normal file
69
chanbans/templates/index.html
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}4chan bans archive - hiddenservice.cc{% endblock title %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<div id="search">
|
||||||
|
<form action="" method="get">
|
||||||
|
<input type="text" name="board" placeholder="Board" value="{{query['board']}}" />
|
||||||
|
<input type="text" name="reason" placeholder="Reason/rule" value="{{query['reason']}}" />
|
||||||
|
<input type="text" name="name" placeholder="Name" value="{{query['name']}}" />
|
||||||
|
<input type="text" name="trip" placeholder="Trip" value="{{query['trip']}}" />
|
||||||
|
<input type="text" name="com" placeholder="Comment" value="{{query['com']}}" />
|
||||||
|
<input type="text" name="sub" placeholder="Subject" value="{{query['sub']}}" />
|
||||||
|
<input type="text" name="md5" placeholder="MD5 Sum" value="{{query['md5']}}" />
|
||||||
|
<input type="submit" value="Search" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<table id="bans-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Post</th>
|
||||||
|
<th>Action</th>
|
||||||
|
<th>Length</th>
|
||||||
|
<th>Reason</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for post in posts %}
|
||||||
|
<tr class="constructed-post">
|
||||||
|
<td align="left">
|
||||||
|
<div class="post reply {% if post['nsfw'] %}nws{% else %}ws{% endif %}">
|
||||||
|
<div class="post-info">
|
||||||
|
<input type="checkbox" disabled>
|
||||||
|
<span class="board"><a href="?board={{post['board']}}">/{{post['board']}}/</a></span>
|
||||||
|
<span class="subject">{{post['sub']|safe}}</span>
|
||||||
|
<span class="name-block">
|
||||||
|
<span class="name">{{post['name']}}</span>
|
||||||
|
{% if post['trip'] %}
|
||||||
|
<span class="poster-trip">{{post['trip']}}</span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="date-time">{{post['now']}}</span>
|
||||||
|
<span class="post-num">No. XXX</span>
|
||||||
|
</div>
|
||||||
|
{% if post['thumb_path'] %}
|
||||||
|
<div class="file">
|
||||||
|
<div class="file-info">
|
||||||
|
<span class="file-text">
|
||||||
|
File: {{post['tim']}}{{post['ext']}}
|
||||||
|
({{post['fsize']}}, {{post['w']}}x{{post['h']}}, {{post['filename']}}{{post['ext']}})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span class="file-thumb">
|
||||||
|
<img src="{{post['thumb_path']}}" data-md5="{{post['md5']}}" loading="lazy">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<blockquote class="post-message">
|
||||||
|
{{post['com']|safe}}
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{post['action']}}</td>
|
||||||
|
<td>{{post['length']}}</td>
|
||||||
|
<td>{{post['reason']}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{# pagination #}
|
||||||
|
{% endblock main %}
|
||||||
80
poetry.lock
generated
80
poetry.lock
generated
@@ -485,6 +485,24 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"
|
|||||||
plugins = ["setuptools"]
|
plugins = ["setuptools"]
|
||||||
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jinja2"
|
||||||
|
version = "3.1.2"
|
||||||
|
description = "A very fast and expressive template engine."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
||||||
|
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
MarkupSafe = ">=2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
i18n = ["Babel (>=2.7)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy-object-proxy"
|
name = "lazy-object-proxy"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@@ -531,6 +549,66 @@ files = [
|
|||||||
{file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"},
|
{file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markupsafe"
|
||||||
|
version = "2.1.3"
|
||||||
|
description = "Safely add untrusted strings to HTML/XML markup."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
|
||||||
|
{file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
|
||||||
|
{file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mccabe"
|
name = "mccabe"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -1007,4 +1085,4 @@ multidict = ">=4.0"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "e73a1335e7f7de402b4ed8fa81acc6580b7c53cf938e17724837ab1dc31ac6de"
|
content-hash = "40f027ee1da4d79b385c85556d6ff43600364194c9352a273c419b96f313a868"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ readme = "README.md"
|
|||||||
python = "^3.9"
|
python = "^3.9"
|
||||||
beautifulsoup4 = "^4.12.2"
|
beautifulsoup4 = "^4.12.2"
|
||||||
aiohttp = "^3.8.4"
|
aiohttp = "^3.8.4"
|
||||||
|
jinja2 = "^3.1.2"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
|||||||
Reference in New Issue
Block a user