Use Ruff and fix all its warnings
This commit is contained in:
61
.pre-commit-config.yaml
Normal file
61
.pre-commit-config.yaml
Normal file
@ -0,0 +1,61 @@
|
||||
default_language_version:
|
||||
python: python3.12
|
||||
repos:
|
||||
# Apply a consistent format to pyproject.toml files.
|
||||
# https://pyproject-fmt.readthedocs.io/en/latest/
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: "1.4.0"
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: v3.1.0
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
||||
|
||||
# Some out-of-the-box hooks for pre-commit.
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-ast
|
||||
- id: check-builtin-literals
|
||||
- id: check-docstring-first
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-merge-conflict
|
||||
- id: check-toml
|
||||
- id: check-vcs-permalinks
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
- id: name-tests-test
|
||||
args: [--pytest-test-first]
|
||||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
exclude_types:
|
||||
- "html"
|
||||
|
||||
# Run Pyupgrade on all Python files. This will upgrade the code to Python 3.12.
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: ["--py312-plus"]
|
||||
|
||||
# An extremely fast Python linter and formatter.
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.3
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
args: ["--fix", "--exit-non-zero-on-fix"]
|
||||
|
||||
# Static checker for GitHub Actions workflow files.
|
||||
- repo: https://github.com/rhysd/actionlint
|
||||
rev: v1.6.26
|
||||
hooks:
|
||||
- id: actionlint
|
||||
|
||||
# Optimize .png files.
|
||||
- repo: https://github.com/shssoichiro/oxipng
|
||||
rev: v9.0.0
|
||||
hooks:
|
||||
- id: oxipng
|
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@ -1,7 +1,4 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
|
@ -1 +0,0 @@
|
||||
__version__ = "1.0.0"
|
||||
|
@ -1,11 +1,19 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from discord_embed import settings
|
||||
|
||||
|
||||
def generate_html_for_videos(url: str, width: int, height: int, screenshot: str, filename: str) -> str:
|
||||
def generate_html_for_videos(
|
||||
url: str,
|
||||
width: int,
|
||||
height: int,
|
||||
screenshot: str,
|
||||
filename: str,
|
||||
) -> str:
|
||||
"""Generate HTML for video files.
|
||||
|
||||
Args:
|
||||
@ -18,10 +26,13 @@ def generate_html_for_videos(url: str, width: int, height: int, screenshot: str,
|
||||
Returns:
|
||||
Returns HTML for video.
|
||||
"""
|
||||
time_now: datetime.datetime = datetime.datetime.now(tz=datetime.UTC)
|
||||
time_now_str: str = time_now.strftime("%Y-%m-%d %H:%M:%S %Z")
|
||||
|
||||
video_html: str = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- Generated at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} -->
|
||||
<!-- Generated at {time_now_str} -->
|
||||
<head>
|
||||
<meta property="og:type" content="video.other">
|
||||
<meta property="twitter:player" content="{url}">
|
||||
@ -39,8 +50,8 @@ def generate_html_for_videos(url: str, width: int, height: int, screenshot: str,
|
||||
# Take the filename and append .html to it.
|
||||
filename += ".html"
|
||||
|
||||
file_path: str = os.path.join(settings.upload_folder, filename)
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
file_path = Path(settings.upload_folder, filename)
|
||||
with Path.open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write(video_html)
|
||||
|
||||
return html_url
|
||||
|
@ -1,8 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from fastapi import FastAPI, File, Request, UploadFile
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from discord_embed import settings
|
||||
@ -17,12 +19,11 @@ app: FastAPI = FastAPI(
|
||||
},
|
||||
)
|
||||
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
templates: Jinja2Templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
||||
@app.post("/uploadfiles/", description="Where to send a POST request to upload files.")
|
||||
async def upload_file(file: UploadFile = File()):
|
||||
async def upload_file(file: UploadFile = File()): # noqa: B008
|
||||
"""Page for uploading files.
|
||||
|
||||
If it is a video, we need to make an HTML file, and a thumbnail
|
||||
@ -35,15 +36,22 @@ async def upload_file(file: UploadFile = File()):
|
||||
Returns:
|
||||
Returns a dict with the filename, or a link to the .html if it was a video.
|
||||
"""
|
||||
if file.filename is None:
|
||||
send_webhook("Filename is None")
|
||||
return JSONResponse(content={"error": "Filename is None"}, status_code=500)
|
||||
if file.content_type is None:
|
||||
send_webhook("Content type is None")
|
||||
return JSONResponse(content={"error": "Content type is None"}, status_code=500)
|
||||
|
||||
if file.content_type.startswith("video/"):
|
||||
html_url: str = await do_things(file)
|
||||
else:
|
||||
filename: str = await remove_illegal_chars(file.filename)
|
||||
|
||||
with open(f"{settings.upload_folder}/{filename}", "wb+") as f:
|
||||
with Path.open(Path(settings.upload_folder, filename), "wb+") as f:
|
||||
f.write(file.file.read())
|
||||
|
||||
html_url: str = urljoin(settings.serve_domain, filename) # type: ignore
|
||||
html_url: str = urljoin(settings.serve_domain, filename)
|
||||
|
||||
send_webhook(f"{html_url} was uploaded.")
|
||||
return JSONResponse(content={"html_url": html_url})
|
||||
@ -58,8 +66,7 @@ async def remove_illegal_chars(file_name: str) -> str:
|
||||
Returns:
|
||||
Returns a string with the filename without illegal characters.
|
||||
"""
|
||||
|
||||
filename: str = file_name.replace(" ", ".") # type: ignore
|
||||
filename: str = file_name.replace(" ", ".")
|
||||
illegal_characters: list[str] = [
|
||||
"*",
|
||||
'"',
|
||||
@ -84,13 +91,31 @@ async def remove_illegal_chars(file_name: str) -> str:
|
||||
",",
|
||||
]
|
||||
for character in illegal_characters:
|
||||
filename: str = filename.replace(character, "") # type: ignore
|
||||
filename: str = filename.replace(character, "")
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
index_html: str = """
|
||||
<html lang="en">
|
||||
|
||||
<body>
|
||||
<h1>discord-nice-embed</h1>
|
||||
<a href="/docs">Swagger UI - API documentation</a>
|
||||
<br />
|
||||
<a href="/redoc">ReDoc - Alternative API documentation</a>
|
||||
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
|
||||
<input name="file" type="file" />
|
||||
<input type="submit" value="Upload file" />
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def main(request: Request):
|
||||
async def main(request: Request): # noqa: ARG001
|
||||
"""Our index view.
|
||||
|
||||
You can upload files here.
|
||||
@ -99,7 +124,6 @@ async def main(request: Request):
|
||||
request: Our request.
|
||||
|
||||
Returns:
|
||||
TemplateResponse: Returns HTML for site.
|
||||
HTMLResponse: Our index.html page.
|
||||
"""
|
||||
|
||||
return templates.TemplateResponse("index.html", {"request": request})
|
||||
return index_html
|
||||
|
@ -1,37 +1,14 @@
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from __future__ import annotations
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import find_dotenv, load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
load_dotenv(find_dotenv(), verbose=True)
|
||||
|
||||
# Check if user has added a domain to the environment.
|
||||
try:
|
||||
serve_domain: str = os.environ["SERVE_DOMAIN"]
|
||||
except KeyError:
|
||||
sys.exit("discord-embed: Environment variable 'SERVE_DOMAIN' is missing!")
|
||||
|
||||
# Remove trailing slash from domain
|
||||
if serve_domain.endswith("/"):
|
||||
serve_domain = serve_domain[:-1]
|
||||
|
||||
# Check if we have a folder for uploads.
|
||||
try:
|
||||
upload_folder: str = os.environ["UPLOAD_FOLDER"]
|
||||
except KeyError:
|
||||
sys.exit("Environment variable 'UPLOAD_FOLDER' is missing!")
|
||||
|
||||
# Create upload_folder if it doesn't exist.
|
||||
pathlib.Path(upload_folder).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Remove trailing slash from upload_folder
|
||||
if upload_folder.endswith("/"):
|
||||
upload_folder = upload_folder[:-1]
|
||||
|
||||
# Discord webhook URL
|
||||
try:
|
||||
webhook_url: str = os.environ["WEBHOOK_URL"]
|
||||
except KeyError:
|
||||
sys.exit("Environment variable 'WEBHOOK_URL' is missing!")
|
||||
webhook_url: str = os.environ["WEBHOOK_URL"]
|
||||
serve_domain: str = os.environ["SERVE_DOMAIN"].removesuffix("/")
|
||||
upload_folder: str = os.environ["UPLOAD_FOLDER"].removesuffix("/")
|
||||
Path(upload_folder).mkdir(parents=True, exist_ok=True)
|
||||
|
@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
|
||||
@ -5,6 +8,8 @@ import ffmpeg
|
||||
|
||||
from discord_embed import settings
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Resolution:
|
||||
@ -28,9 +33,12 @@ def video_resolution(path_to_video: str) -> Resolution:
|
||||
Returns height and width.
|
||||
"""
|
||||
probe = ffmpeg.probe(path_to_video)
|
||||
video_stream = next((stream for stream in probe["streams"] if stream["codec_type"] == "video"), None)
|
||||
video_stream = next(
|
||||
(stream for stream in probe["streams"] if stream["codec_type"] == "video"),
|
||||
None,
|
||||
)
|
||||
if video_stream is None:
|
||||
print("No video stream found", file=sys.stderr)
|
||||
logger.critical("No video stream found")
|
||||
sys.exit(1)
|
||||
|
||||
width: int = int(video_stream["width"])
|
||||
|
@ -1,13 +1,16 @@
|
||||
import os
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import UploadFile
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from discord_embed import settings
|
||||
from discord_embed.generate_html import generate_html_for_videos
|
||||
from discord_embed.video import Resolution, make_thumbnail, video_resolution
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastapi import UploadFile
|
||||
|
||||
|
||||
@dataclass
|
||||
class VideoFile:
|
||||
@ -32,19 +35,23 @@ def save_to_disk(file: UploadFile) -> VideoFile:
|
||||
Returns:
|
||||
VideoFile object with the filename and location.
|
||||
"""
|
||||
if file.filename is None:
|
||||
msg = "Filename is None"
|
||||
raise ValueError(msg)
|
||||
|
||||
# Create the folder where we should save the files
|
||||
folder_video: str = os.path.join(settings.upload_folder, "video")
|
||||
Path(folder_video).mkdir(parents=True, exist_ok=True)
|
||||
save_folder_video = Path(settings.upload_folder, "video")
|
||||
Path(save_folder_video).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Replace spaces with dots in the filename.
|
||||
filename: str = file.filename.replace(" ", ".")
|
||||
|
||||
# Save the uploaded file to disk.
|
||||
file_location: str = os.path.join(folder_video, filename)
|
||||
with open(file_location, "wb+") as f:
|
||||
file_location = Path(save_folder_video, filename)
|
||||
with Path.open(file_location, "wb+") as f:
|
||||
f.write(file.file.read())
|
||||
|
||||
return VideoFile(filename, file_location)
|
||||
return VideoFile(filename, str(file_location))
|
||||
|
||||
|
||||
async def do_things(file: UploadFile) -> str:
|
||||
@ -56,7 +63,6 @@ async def do_things(file: UploadFile) -> str:
|
||||
Returns:
|
||||
Returns URL for video.
|
||||
"""
|
||||
|
||||
video_file: VideoFile = save_to_disk(file)
|
||||
|
||||
file_url: str = f"{settings.serve_domain}/video/{video_file.filename}"
|
||||
|
@ -1,7 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from discord_webhook import DiscordWebhook
|
||||
|
||||
from discord_embed import settings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from requests import Response
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def send_webhook(message: str) -> None:
|
||||
"""Send a webhook to Discord.
|
||||
@ -11,7 +21,9 @@ def send_webhook(message: str) -> None:
|
||||
"""
|
||||
webhook: DiscordWebhook = DiscordWebhook(
|
||||
url=settings.webhook_url,
|
||||
content=message,
|
||||
content=message or "discord-nice-embed: No message was provided.",
|
||||
rate_limit_retry=True,
|
||||
)
|
||||
webhook.execute()
|
||||
response: Response = webhook.execute()
|
||||
if not response.ok:
|
||||
logger.critical("Webhook failed to send\n %s\n %s", response, message)
|
||||
|
137
poetry.lock
generated
137
poetry.lock
generated
@ -42,6 +42,17 @@ files = [
|
||||
{file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.4.0"
|
||||
description = "Validate configuration and produce human readable error messages."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
|
||||
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.3.2"
|
||||
@ -183,6 +194,17 @@ requests = ">=2.28.1,<3.0.0"
|
||||
[package.extras]
|
||||
async = ["httpx (>=0.23.0,<0.24.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.3.7"
|
||||
description = "Distribution utilities"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"},
|
||||
{file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.104.1"
|
||||
@ -220,6 +242,22 @@ future = "*"
|
||||
[package.extras]
|
||||
dev = ["Sphinx (==2.1.0)", "future (==0.17.1)", "numpy (==1.16.4)", "pytest (==4.6.1)", "pytest-mock (==1.10.4)", "tox (==3.12.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.13.1"
|
||||
description = "A platform independent file lock."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"},
|
||||
{file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"]
|
||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
|
||||
typing = ["typing-extensions (>=4.8)"]
|
||||
|
||||
[[package]]
|
||||
name = "future"
|
||||
version = "0.18.3"
|
||||
@ -333,6 +371,20 @@ cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.5.31"
|
||||
description = "File identification library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "identify-2.5.31-py2.py3-none-any.whl", hash = "sha256:90199cb9e7bd3c5407a9b7e81b4abec4bb9d249991c79439ec8af740afc6293d"},
|
||||
{file = "identify-2.5.31.tar.gz", hash = "sha256:7736b3c7a28233637e3c36550646fc6389bedd74ae84cb788200cc8e2dd60b75"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
license = ["ukkonen"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.4"
|
||||
@ -441,6 +493,20 @@ files = [
|
||||
{file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.8.0"
|
||||
description = "Node.js virtual environment builder"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
|
||||
files = [
|
||||
{file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
|
||||
{file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
setuptools = "*"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.2"
|
||||
@ -452,6 +518,21 @@ files = [
|
||||
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "3.11.0"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
|
||||
{file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.3.0"
|
||||
@ -467,6 +548,24 @@ files = [
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "3.5.0"
|
||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"},
|
||||
{file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cfgv = ">=2.0.0"
|
||||
identify = ">=1.0.0"
|
||||
nodeenv = ">=0.11.1"
|
||||
pyyaml = ">=5.1"
|
||||
virtualenv = ">=20.10.0"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.4.2"
|
||||
@ -732,6 +831,22 @@ urllib3 = ">=1.21.1,<3"
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "68.2.2"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"},
|
||||
{file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.0"
|
||||
@ -857,6 +972,26 @@ files = [
|
||||
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
|
||||
test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.24.6"
|
||||
description = "Virtual Python Environment builder"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"},
|
||||
{file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
distlib = ">=0.3.7,<1"
|
||||
filelock = ">=3.12.2,<4"
|
||||
platformdirs = ">=3.9.1,<4"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
|
||||
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "0.21.0"
|
||||
@ -1028,4 +1163,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "3eb51d6b10b9c56043cfeaf3cffbe0f30f526ff774b2c4bbd7672f5e3a0d6b03"
|
||||
content-hash = "939a371df3b2147c6a306b152b4f7011fc0663defba72c79085f705b0eaeef9c"
|
||||
|
@ -18,16 +18,20 @@ uvicorn = { extras = ["standard"], version = "^0.23.0" }
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
httpx = "^0.25.0"
|
||||
pytest = "^7.4.3"
|
||||
pre-commit = "^3.5.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
requires = [
|
||||
"poetry-core>=1",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
fix = true
|
||||
unsafe-fixes = true
|
||||
preview = true
|
||||
select = ["ALL"]
|
||||
ignore = ["D100", "CPY001"]
|
||||
ignore = ["D100", "D104", "CPY001", "ANN201"]
|
||||
|
||||
[tool.ruff.pydocstyle]
|
||||
convention = "google"
|
||||
|
@ -1,12 +0,0 @@
|
||||
body {
|
||||
background-color: #111111;
|
||||
color: #DCDDDE;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #00AFF4;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #00AFF4;
|
||||
}
|
@ -1,21 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>discord-nice-embed</title>
|
||||
<meta content="text/html; charset=utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<link href="static/style.css" rel="stylesheet"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>discord-nice-embed</h1>
|
||||
<a href="/docs">Swagger UI - API documentation</a>
|
||||
<br/>
|
||||
<a href="/redoc">ReDoc - Alternative API documentation</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
|
||||
<input name="file" type="file"/>
|
||||
<input type="submit" value="Upload file"/>
|
||||
</form>
|
||||
<h1>discord-nice-embed</h1>
|
||||
<a href="/docs">Swagger UI - API documentation</a>
|
||||
<br />
|
||||
<a href="/redoc">ReDoc - Alternative API documentation</a>
|
||||
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
|
||||
<input name="file" type="file" />
|
||||
<input type="submit" value="Upload file" />
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -1,68 +1,44 @@
|
||||
import os
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from fastapi import Response
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from discord_embed import __version__, settings
|
||||
from discord_embed import settings
|
||||
from discord_embed.main import app
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import httpx
|
||||
|
||||
client: TestClient = TestClient(app)
|
||||
TEST_FILE: str = "tests/test.mp4"
|
||||
|
||||
|
||||
def test_version() -> None:
|
||||
"""Test version is correct."""
|
||||
assert __version__ == "1.0.0"
|
||||
|
||||
|
||||
def test_domain_ends_with_slash() -> None:
|
||||
"""Test domain ends with a slash."""
|
||||
assert not settings.serve_domain.endswith("/")
|
||||
|
||||
|
||||
def test_save_to_disk() -> None:
|
||||
"""Test save_to_disk() works."""
|
||||
# TODO: Implement this test. I need to mock the UploadFile object.
|
||||
|
||||
|
||||
def test_do_things() -> None:
|
||||
"""Test do_things() works."""
|
||||
# TODO: Implement this test. I need to mock the UploadFile object.
|
||||
|
||||
|
||||
def test_main() -> None:
|
||||
"""Test main() works."""
|
||||
data_without_trailing_nl = ""
|
||||
response: Response = client.get("/")
|
||||
|
||||
# Check if response is our HTML.
|
||||
with open("templates/index.html", encoding="utf8") as our_html:
|
||||
data: str = our_html.read()
|
||||
|
||||
# index.html has a trailing newline that we need to remove.
|
||||
if data[-1:] == "\n":
|
||||
data_without_trailing_nl: str = data[:-1] # type: ignore
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.text == data_without_trailing_nl
|
||||
response: httpx.Response = client.get("/")
|
||||
assert response.is_success
|
||||
|
||||
|
||||
def test_upload_file() -> None:
|
||||
"""Test if we can upload files."""
|
||||
domain = os.environ["SERVE_DOMAIN"]
|
||||
|
||||
# Remove trailing slash from domain
|
||||
if domain.endswith("/"):
|
||||
domain: str = domain[:-1] # type: ignore
|
||||
domain = os.environ["SERVE_DOMAIN"].removesuffix("/")
|
||||
|
||||
# Upload our video file and check if it returns the html_url.
|
||||
with open(TEST_FILE, "rb") as uploaded_file:
|
||||
response: Response = client.post(
|
||||
with Path.open(Path(TEST_FILE), "rb") as uploaded_file:
|
||||
response: httpx.Response = client.post(
|
||||
url="/uploadfiles/",
|
||||
files={"file": uploaded_file},
|
||||
)
|
||||
returned_json = response.json()
|
||||
html_url: str = returned_json["html_url"]
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.is_success
|
||||
assert html_url == f"{domain}/test.mp4"
|
||||
|
@ -1,4 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from discord_embed.generate_html import generate_html_for_videos
|
||||
|
||||
@ -12,8 +15,8 @@ def test_generate_html_for_videos() -> None:
|
||||
domain = domain[:-1]
|
||||
|
||||
# Delete the old HTML file if it exists
|
||||
if os.path.exists("Uploads/test_video.mp4.html"):
|
||||
os.remove("Uploads/test_video.mp4.html")
|
||||
if Path.exists(Path("Uploads/test_video.mp4.html")):
|
||||
Path.unlink(Path("Uploads/test_video.mp4.html"))
|
||||
|
||||
generated_html: str = generate_html_for_videos(
|
||||
url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||
@ -23,58 +26,3 @@ def test_generate_html_for_videos() -> None:
|
||||
filename="test_video.mp4",
|
||||
)
|
||||
assert generated_html == f"{domain}/test_video.mp4"
|
||||
|
||||
# Open the generated HTML and check if it contains the correct URL, width, height, and screenshot.
|
||||
|
||||
with open("Uploads/test_video.mp4.html", "r") as generated_html_file:
|
||||
generated_html_lines: list[str] = generated_html_file.readlines()
|
||||
"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- Generated at 2022-08-08 08:16:53 -->
|
||||
<head>
|
||||
<meta property="og:type" content="video.other">
|
||||
<meta property="twitter:player" content="https://www.youtube.com/watch?v=dQw4w9WgXcQ">
|
||||
<meta property="og:video:type" content="text/html">
|
||||
<meta property="og:video:width" content="1920">
|
||||
<meta property="og:video:height" content="1080">
|
||||
<meta name="twitter:image" content="https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg">
|
||||
<meta http-equiv="refresh" content="0;url=https://www.youtube.com/watch?v=dQw4w9WgXcQ">
|
||||
</head>
|
||||
</html>
|
||||
"""
|
||||
|
||||
for line, html in enumerate(generated_html_lines):
|
||||
# Strip spaces and newlines
|
||||
stripped_html: str = html.strip()
|
||||
|
||||
rick: str = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
|
||||
# Check each line
|
||||
if line == 1:
|
||||
assert stripped_html == "<!DOCTYPE html>"
|
||||
elif line == 2:
|
||||
assert stripped_html == "<html>"
|
||||
elif line == 3:
|
||||
assert stripped_html.startswith("<!-- Generated at ")
|
||||
elif line == 4:
|
||||
assert stripped_html == "<head>"
|
||||
elif line == 5:
|
||||
assert stripped_html == '<meta property="og:type" content="video.other">'
|
||||
elif line == 6:
|
||||
assert stripped_html == f'<meta property="twitter:player" content="{rick}">'
|
||||
elif line == 7:
|
||||
assert stripped_html == '<meta property="og:video:type" content="text/html">'
|
||||
elif line == 8:
|
||||
assert stripped_html == '<meta property="og:video:width" content="1920">'
|
||||
elif line == 9:
|
||||
assert stripped_html == '<meta property="og:video:height" content="1080">'
|
||||
elif line == 10:
|
||||
thumb: str = "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg"
|
||||
assert stripped_html == f'<meta name="twitter:image" content="{thumb}">'
|
||||
elif line == 11:
|
||||
assert stripped_html == f'<meta http-equiv="refresh" content="0;url={rick}">'
|
||||
elif line == 12:
|
||||
assert stripped_html == "</head>"
|
||||
elif line == 13:
|
||||
assert stripped_html == "</html>"
|
||||
|
@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import imghdr
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from discord_embed import settings
|
||||
from discord_embed.video import Resolution, make_thumbnail, video_resolution
|
||||
@ -18,11 +21,11 @@ def test_make_thumbnail() -> None:
|
||||
|
||||
# Remove trailing slash from domain
|
||||
if domain.endswith("/"):
|
||||
domain: str = domain[:-1] # type: ignore
|
||||
domain: str = domain[:-1]
|
||||
|
||||
# Remove thumbnail if it exists
|
||||
if os.path.exists(f"{settings.upload_folder}/test.mp4.jpg"):
|
||||
os.remove(f"{settings.upload_folder}/test.mp4.jpg")
|
||||
if Path.exists(Path(f"{settings.upload_folder}/test.mp4.jpg")):
|
||||
Path.unlink(Path(f"{settings.upload_folder}/test.mp4.jpg"))
|
||||
|
||||
thumbnail: str = make_thumbnail(TEST_FILE, "test.mp4")
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from discord_embed.webhook import send_webhook
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user