structure: refactor codebase

This commit is contained in:
ZhymabekRoman 2024-09-20 18:39:12 +05:00
parent 213d41df01
commit c0ffc87932
101 changed files with 821 additions and 49714 deletions

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "server/toolkits/core"]
path = server/toolkits/core
url = https://github.com/Freedium-cfd/core

View file

@ -1,7 +1,9 @@
FROM python:3.12.3-slim
FROM python:3.12.3
# -slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && apt upgrade -y && apt install gcc -y && apt clean # && rm /var/lib/apt/lists/*
RUN pip install poetry && poetry config virtualenvs.create false
WORKDIR /app
@ -16,14 +18,12 @@ RUN pip3 install --no-cache-dir ./database-lib
COPY ./core ./core
RUN pip3 install --no-cache-dir ./core
# COPY ./server ./server
COPY ./web ./web
COPY ./requirements.txt ./
RUN pip3 install --no-cache-dir -r requirements.txt
WORKDIR /app/web
COPY ./requirements-fast.txt ./
RUN pip3 install --no-cache-dir -r requirements-fast.txt
RUN poetry install
EXPOSE 7080
# EXPOSE 7080
CMD ["python3", "-m", "server", "server"]

View file

@ -1,4 +0,0 @@
FROM shturman/dante:1.4.2
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && apt upgrade -y && apt install -y curl && apt clean && rm -rf /var/lib/apt/lists/*

View file

@ -8,10 +8,6 @@
## FAQ
### What is happened to GitHub organization?
Our whole Github organization is not public for now. Reddit community, that was beginning all of that unfourtunately also gone. So we have moved to Codeberg
### Why did we create Freedium?
In mid-June to mid-July 2023, Medium changed their paywall method, and all old paywall bypass methods we had stopped working. So I became obsessed with the idea of creating a service to bypass Medium's paywalled posts. Honestly I am not a big fan of Medium, but I sometimes read articles to improve my knowledge.
@ -20,27 +16,20 @@ In mid-June to mid-July 2023, Medium changed their paywall method, and all old p
In the first version of Freedium, we reverse-engineered Medium.com's GraphQL endpoints and built our own parser and toolkits to show you unpaywalled Medium posts. Unfortunately, Medium closed this loophole and nowadays we just pay subscriptions and share access through Freedium. Sometimes we got a bugs because of the self-written parser, but we are working to make Freedium bug-free.
### What language are being used?
We use Python, with Jinja template builder, and some JS magic in Frontend :)
### Wow! I would like to contribute to Freedium. How can I do that?
We need volunteers who have Medium subscriptions because we might get banned by Medium. And if you developer you can start from the this (https://codeberg.org/Freedium-cfd/web) repository.
### Plans, future?
Speed up Freedium, and probably create open source Medium frontend in next life
Speed up Freedium, add support for more services than just Medium and (probably) create open source Medium frontend (in next life)
## Tech stack:
## Technologies:
- FastAPI, Gunicorn, Unicorn as worker,
- Tailwinds CSS v3
- Dragonfly (Redis like key-value database)
- Jinja2
- Python 3.9+
- Caddy
- Sentry
- Backend: Python 3.9+, Unicorn, FastAPI, Jinja2, Sentry
- Frontend: Tailwinds CSS v3
- Database: PostgreSQL, Dragonfly (Redis and Memcached compatible key-value database)
- Utils: Caddy, Docker, Docker Compose, Cloudflare WARP proxy (wgcf)
## Local run:
@ -50,18 +39,38 @@ Requirements:
- git
- Linux. Officially, we can't guarantee that Freedium will work on other OS.
We need configure our Freedium instance. Copy `.env_template` to `.env` configuration file and set values, required for you.
To configure your Freedium instance, follow these steps:
```
git clone https://codeberg.org/Freedium-cfd/web/ ./web --depth 1
cd ./web
cp .env_template .env
# do some changes in .env, if you want
sudo docker-compose -f docker-compose-dev.yml up
```
1. Clone the repository:
```
git clone https://codeberg.org/Freedium-cfd/web/ ./web --depth 1
cd ./web
```
2. Create and configure the environment file:
```
cp .env_template .env
```
Open the `.env` file and adjust the values as needed for your setup.
3. Set up the Docker network:
```
sudo docker network create caddy_net
```
4. Start the Freedium services:
```
sudo docker compose -f ./docker-compose/docker-compose.yml up
```
And now you can access local instance of Freedium by opening browser and type `http://localhost:6752`.
These steps will set up and run your local Freedium instance.
## TODO:
- Integrate library notifiers - https://github.com/liiight/notifiers

View file

@ -106,12 +106,6 @@
}
handle_path /sitemap.xml {
root * /static/sitemap.xml
file_server
}
handle_path /websocket {
respond "Access denied" 403
}
@ -208,6 +202,6 @@
route /* {
reverse_proxy web:7080
reverse_proxy freedium_web:7080
}
}

View file

@ -6,6 +6,6 @@
{{ template }}
route /* {
reverse_proxy web:7080
reverse_proxy freedium_web:7080
}
}

View file

@ -107,12 +107,6 @@ freedium.cfd {
}
handle_path /sitemap.xml {
root * /static/sitemap.xml
file_server
}
handle_path /websocket {
respond "Access denied" 403
}
@ -209,6 +203,6 @@ freedium.cfd {
route /* {
reverse_proxy web:7080
reverse_proxy freedium_web:7080
}
}

View file

@ -7,6 +7,6 @@ freedium.cfd {
{{ template }}
route /* {
reverse_proxy web:7080
reverse_proxy freedium_web:7080
}
}

View file

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View file

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 7 KiB

View file

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -1,3 +0,0 @@
djlint==1.32.1
ruff==0.0.261
black==23.7.0

View file

@ -9,4 +9,5 @@ bs4==0.0.1
Jinja2==3.1.2
beautifulsoup4==4.12.2
async-lru==2.0.4
orjson==3.10.6
asyncer

View file

@ -1,282 +0,0 @@
# https://blog.thelazyfox.xyz/how-to-create-healthchecks-for-docker/
version: '3.7'
services:
caddy_freedium:
build:
context: ./
dockerfile: ./DockerfileCaddy
# container_name: caddy_freedium
cap_add:
- NET_ADMIN
ports:
- "6752:6752"
volumes:
- ./CaddyfileDev:/etc/caddy/Caddyfile # - ./CaddyfileMaintance:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
- ./static:/static
networks:
- caddy_net
healthcheck:
test: [ "CMD-SHELL", "curl -f http://localhost:6752/ --max-time 80 --header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15'" ]
interval: 30s
start_period: 20s
timeout: 80s
retries: 3
# restart: always
stop_grace_period: 2m
web:
build:
context: ./
dockerfile: ./Dockerfile
command: python3 -m server server
environment:
- "PROXY_LIST=socks5://wgcf1:1080,socks5://wgcf2:1080"
volumes:
# - ./.env:/app/.env
# - ./server/user_data/logs:/app/server/user_data/logs
- .:/app
- ./core/medium_parser/:/app/medium_parser
- ./core/rl_string_helper/:/app/rl_string_helper
# develop:
# watch:
# - action: rebuild
# path: ./server
# target: /app/server
# - action: rebuild
# path: ./core
# target: /app/core
# - action: rebuild
# path: ./rl_string_helper
# target: /app/rl_string_helper
# - action: rebuild
# path: ./database-lib
# target: /app/database-lib
# - action: rebuild
# path: "**/requirements.txt"
# - action: rebuild
# path: "**/requirements-fast.txt"
ports:
- "7080:7080"
networks:
- caddy_net
# healthcheck:
# test: ["CMD-SHELL", "curl -f http://web:7080/ --max-time 80 --header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15'"]
# interval: 30s
# start_period: 20s
# timeout: 80s
# retries: 3
# restart: always
mem_limit: 4g
stop_grace_period: 2m
########## WGCF PROXY ----1---- ##########
wgcf1:
image: neilpang/wgcf-docker:latest
container_name: wgcf1
volumes:
# - ./wgcf:/wgcf
- /lib/modules:/lib/modules
- ./scripts/entryWGCF.sh:/entry.sh
- /etc/localtime:/etc/localtime:ro
privileged: true
sysctls:
net.ipv6.conf.all.disable_ipv6: 0
cap_add:
- NET_ADMIN
# ports:
# - "9681:1080"
restart: always
networks:
- caddy_net
healthcheck:
test: curl -fs https://www.cloudflare.com/cdn-cgi/trace | grep -q -E 'warp=(on|plus)' && exit 0 || exit 1
interval: 5s
timeout: 2s
retries: 10000
dante_1:
image: shturman/dante:1.4.2
container_name: dante_1
# build:
# context: ./
# dockerfile: ./DockerfileDante
volumes:
- ./scripts/dante.config:/etc/sockd.conf
- /etc/localtime:/etc/localtime:ro
restart: always
environment:
- CFGFILE=/etc/sockd.conf
network_mode: "service:wgcf1"
depends_on:
wgcf1:
condition: service_healthy
# healthcheck:
# test: curl --proxy socks5://localhost:1080 https://google.com
# interval: 5s
# timeout: 2s
# retries: 5
wgcf1_healthcare_service:
build:
context: ./scripts/wgcf-healthcare
dockerfile: Dockerfile
environment:
- TG_TOKEN=${TELEGRAM_BOT_TOKEN}
- TG_CHAT_ID=${TELEGRAM_ADMIN_ID}
- CONTAINERS_TO_RESTART=wgcf1,dante_1
- PROXY_HOST=wgcf1
- PROXY_PORT=1080
- SLEEP_DURATION=30
# depends_on:
# - dante_1
restart: always
networks:
- caddy_net
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/localtime:/etc/localtime:ro
# network_mode: "host"
########## WGCF PROXY ----2---- ##########
wgcf2:
image: neilpang/wgcf-docker:latest
container_name: wgcf2
volumes:
# - ./wgcf:/wgcf
- /lib/modules:/lib/modules
- ./scripts/entryWGCF.sh:/entry.sh
- /etc/localtime:/etc/localtime:ro
privileged: true
sysctls:
net.ipv6.conf.all.disable_ipv6: 0
depends_on:
wgcf1:
condition: service_healthy
cap_add:
- NET_ADMIN
# ports:
# - "9681:1080"
restart: always
networks:
- caddy_net
healthcheck:
test: curl -fs https://www.cloudflare.com/cdn-cgi/trace | grep -q -E 'warp=(on|plus)' && exit 0 || exit 1
interval: 5s
timeout: 2s
retries: 10000
dante_2:
image: shturman/dante:1.4.2
container_name: dante_2
# build:
# context: ./
# dockerfile: ./DockerfileDante
volumes:
- ./scripts/dante.config:/etc/sockd.conf
- /etc/localtime:/etc/localtime:ro
restart: always
environment:
- CFGFILE=/etc/sockd.conf
network_mode: "service:wgcf2"
depends_on:
wgcf2:
condition: service_healthy
# healthcheck:
# test: curl --proxy socks5://localhost:1080 https://google.com
# interval: 5s
# timeout: 2s
# retries: 5
wgcf2_healthcare_service:
build:
context: ./scripts/wgcf-healthcare
dockerfile: Dockerfile
environment:
- TG_TOKEN=${TELEGRAM_BOT_TOKEN}
- TG_CHAT_ID=${TELEGRAM_ADMIN_ID}
- CONTAINERS_TO_RESTART=wgcf2,dante_2
- PROXY_HOST=wgcf2
- PROXY_PORT=1080
- SLEEP_DURATION=40
# depends_on:
# - dante_2
restart: always
networks:
- caddy_net
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/localtime:/etc/localtime:ro
# network_mode: "host"
# redis_service:
# image: redis:latest
# networks:
# - caddy_net
# healthcheck:
# test: ["CMD", "redis-cli", "ping"]
# interval: 30s
# start_period: 20s
# timeout: 10s
# retries: 3
# restart: always
# stop_grace_period: 2m
redis_service:
image: 'docker.dragonflydb.io/dragonflydb/dragonfly'
ulimits:
memlock: -1
# expose:
# - 6379
networks:
- caddy_net
volumes:
- dragonflydata:/data
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 30s
start_period: 20s
timeout: 10s
retries: 3
restart: always
stop_grace_period: 2m
mem_limit: 2g
postgres:
image: postgres:16.3-alpine3.20
networks:
- caddy_net
# ports:
# - 5432:5432
volumes:
# - ~/apps/postgres:/var/lib/postgresql/data
- ./postgres:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_USER=postgres
- POSTGRES_DB=postgres
stop_grace_period: 2m
autoheal:
restart: always
image: willfarrell/autoheal
environment:
- AUTOHEAL_CONTAINER_LABEL=all
volumes:
- /var/run/docker.sock:/var/run/docker.sock
stop_grace_period: 2m
volumes:
caddy_data:
caddy_config:
dragonflydata:
networks:
caddy_net:
external: true

View file

@ -0,0 +1,37 @@
version: '3.7'
services:
redis_service:
container_name: redis_service
image: 'docker.dragonflydb.io/dragonflydb/dragonfly'
ulimits:
memlock: -1
networks:
- freedium_net
volumes:
- freedium_dragonflydata:/data
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 30s
start_period: 20s
timeout: 10s
retries: 3
restart: always
stop_grace_period: 2m
mem_limit: 2g
postgres:
image: postgres:16.3-alpine3.20
container_name: freedium_postgres
networks:
- freedium_net
volumes:
- ./postgres:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_USER=postgres
- POSTGRES_DB=postgres
stop_grace_period: 2m
volumes:
freedium_dragonflydata:

View file

@ -0,0 +1,55 @@
version: '3.7'
services:
caddy_freedium:
container_name: caddy_freedium
build:
context: ../caddy
dockerfile: Dockerfile
cap_add:
- NET_ADMIN
ports:
- "6752:6752"
volumes:
- ../caddy/CaddyfileDev:/etc/caddy/Caddyfile
- freedium_caddy_data:/data
- freedium_caddy_config:/config
- ../caddy/static:/static
networks:
- freedium_net
- caddy_net
healthcheck:
test: [ "CMD-SHELL", "curl -f http://localhost:6752/ --max-time 80 --header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15'" ]
interval: 30s
start_period: 20s
timeout: 80s
retries: 3
stop_grace_period: 2m
freedium_web:
container_name: freedium_web
build:
context: ../
dockerfile: Dockerfile
environment:
- "PROXY_LIST=socks5://wgcf1:1080,socks5://wgcf2:1080"
# volumes:
# - ./web:/app/web
# - ./core/medium_parser/:/app/medium_parser
# - ./core/rl_string_helper/:/app/rl_string_helper
ports:
- "7080:7080"
networks:
- freedium_net
mem_limit: 4g
stop_grace_period: 2m
volumes:
freedium_caddy_data:
freedium_caddy_config:
networks:
caddy_net:
external: true
freedium_net:

View file

@ -0,0 +1,12 @@
version: '3.7'
services:
autoheal:
container_name: freedium_autoheal
restart: always
image: willfarrell/autoheal
environment:
- AUTOHEAL_CONTAINER_LABEL=all
volumes:
- /var/run/docker.sock:/var/run/docker.sock
stop_grace_period: 2m

View file

@ -0,0 +1,111 @@
version: '3.7'
services:
wgcf1:
image: neilpang/wgcf-docker:latest
container_name: wgcf1
volumes:
- /lib/modules:/lib/modules
- /etc/localtime:/etc/localtime:ro
- ../wgcf/entry.sh:/entry.sh
privileged: true
sysctls:
net.ipv6.conf.all.disable_ipv6: 0
cap_add:
- NET_ADMIN
restart: always
networks:
- freedium_net
healthcheck:
test: curl -fs https://www.cloudflare.com/cdn-cgi/trace | grep -q -E 'warp=(on|plus)' && exit 0 || exit 1
interval: 5s
timeout: 2s
retries: 10000
dante_1:
image: shturman/dante:1.4.2
container_name: dante_1
volumes:
- ../wgcf/dante/sockd.conf:/etc/sockd.conf
- /etc/localtime:/etc/localtime:ro
restart: always
environment:
- CFGFILE=/etc/sockd.conf
network_mode: "service:wgcf1"
depends_on:
wgcf1:
condition: service_healthy
wgcf1_healthcare_service:
build:
context: ../wgcf/wgcf-healthcare
dockerfile: Dockerfile
environment:
- TG_TOKEN=${TELEGRAM_BOT_TOKEN}
- TG_CHAT_ID=${TELEGRAM_ADMIN_ID}
- CONTAINERS_TO_RESTART=wgcf1,dante_1
- PROXY_HOST=wgcf1
- PROXY_PORT=1080
- SLEEP_DURATION=30
restart: always
networks:
- freedium_net
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/localtime:/etc/localtime:ro
wgcf2:
image: neilpang/wgcf-docker:latest
container_name: wgcf2
volumes:
- /lib/modules:/lib/modules
- /etc/localtime:/etc/localtime:ro
- ../wgcf/entry.sh:/entry.sh
privileged: true
sysctls:
net.ipv6.conf.all.disable_ipv6: 0
depends_on:
wgcf1:
condition: service_healthy
cap_add:
- NET_ADMIN
restart: always
networks:
- freedium_net
healthcheck:
test: curl -fs https://www.cloudflare.com/cdn-cgi/trace | grep -q -E 'warp=(on|plus)' && exit 0 || exit 1
interval: 5s
timeout: 2s
retries: 10000
dante_2:
image: shturman/dante:1.4.2
container_name: dante_2
volumes:
- ../wgcf/dante/sockd.conf:/etc/sockd.conf
- /etc/localtime:/etc/localtime:ro
restart: always
environment:
- CFGFILE=/etc/sockd.conf
network_mode: "service:wgcf2"
depends_on:
wgcf2:
condition: service_healthy
wgcf2_healthcare_service:
build:
context: ../wgcf/wgcf-healthcare
dockerfile: Dockerfile
environment:
- TG_TOKEN=${TELEGRAM_BOT_TOKEN}
- TG_CHAT_ID=${TELEGRAM_ADMIN_ID}
- CONTAINERS_TO_RESTART=wgcf2,dante_2
- PROXY_HOST=wgcf2
- PROXY_PORT=1080
- SLEEP_DURATION=40
restart: always
networks:
- freedium_net
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/localtime:/etc/localtime:ro

View file

@ -0,0 +1,7 @@
version: '3.7'
include:
- docker-compose.main.yml
- docker-compose.wgcf.yml
- docker-compose.db.yml
- docker-compose.utility.yml

View file

@ -1,82 +0,0 @@
# https://blog.thelazyfox.xyz/how-to-create-healthchecks-for-docker/
version: '3.7'
services:
caddy:
build:
context: ./
dockerfile: ./DockerfileCaddy
cap_add:
- NET_ADMIN
ports:
- "80:80"
- "443:443"
volumes:
- ./CaddyfileProd:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- web_network
healthcheck:
test: ["CMD-SHELL", "curl http://caddy:80/ --max-time 80"]
interval: 30s
start_period: 20s
timeout: 80s
retries: 3
restart: always
web:
build:
context: ./
dockerfile: ./Dockerfile
command: python3 -m server server
volumes:
- .:/app
expose:
- 7080
networks:
- web_network
healthcheck:
test: ["CMD-SHELL", "curl http://localhost:7080/ --max-time 60"]
interval: 30s
start_period: 20s
timeout: 80s
retries: 3
restart: always
mem_limit: 2g
dragonfly:
image: 'docker.dragonflydb.io/dragonflydb/dragonfly'
ulimits:
memlock: -1
expose:
- 6379
networks:
- web_network
volumes:
- dragonflydata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
start_period: 20s
timeout: 10s
retries: 3
restart: always
autoheal:
restart: always
image: willfarrell/autoheal
environment:
- AUTOHEAL_CONTAINER_LABEL=all
volumes:
- /var/run/docker.sock:/var/run/docker.sock
volumes:
caddy_data:
caddy_config:
dragonflydata:
networks:
web_network:
driver: bridge

View file

@ -1,3 +0,0 @@
djlint==1.32.1
ruff==0.0.261
black==23.7.0

View file

@ -1 +0,0 @@
orjson==3.10.6

View file

@ -1,12 +0,0 @@
pickledb==0.9.2
html5lib==1.1
sentry-sdk[fastapi]==1.29.2
loguru==0.6.0 # due to: https://github.com/Delgan/loguru/issues/916
uvicorn[standard]==0.30.3
anyio<=4.0.0 # Workaround to: https://github.getafreenode.com/tiangolo/fastapi/discussions/11652
Jinja2==3.1.2
fastapi==0.108.0
gunicorn==21.2.0
redis[hiredis]==4.6.0
xkcdpass==1.19.3
apscheduler==3.10.4

View file

@ -1,9 +1,10 @@
from loguru import logger
from .logger_trace import trace
from .utils import quote_html, quote_symbol
from jinja2 import Environment, DebugUndefined, Template
from rl_string_helper.mixins.string_assignment import StringAssignmentMixin_py as StringAssignmentMixin
from rl_string_helper.mixins.string_assignment import (
StringAssignmentMixin_py as StringAssignmentMixin,
)
jinja_env = Environment(undefined=DebugUndefined)
@ -21,9 +22,21 @@ Python uses UTF-8 encoding, which each character is encoded as one byte. So here
# TODO: doc! Who will read this noodles lol?
# TODO: check cases when UTF-16 character can be more that 2 bytes
class RLStringHelper:
__slots__ = ("string", "templates", "replaces", "quote_html_type", "quote_replaces", "_default_bang_char")
__slots__ = (
"string",
"templates",
"replaces",
"quote_html_type",
"quote_replaces",
"_default_bang_char",
)
def __init__(self, string: str, quote_html_type: list[str] = ["full"], _default_bang_char: str = "R"):
def __init__(
self,
string: str,
quote_html_type: list[str] = ["full"],
_default_bang_char: str = "R",
):
self.string: str = quote_symbol(string)
self.templates = []
self.quote_replaces = []
@ -31,7 +44,6 @@ class RLStringHelper:
self.quote_html_type = quote_html_type
self._default_bang_char = _default_bang_char
@trace
def pre_utf_16_bang(self, string: str, string_pos_matrix: list):
utf_16_bang_list = []
string_len_utf_16 = len(string.encode("utf-16-le")) // 2
@ -47,7 +59,9 @@ class RLStringHelper:
if char_len == 2:
char_len_dif = char_len - 1
char_present = self._default_bang_char * char_len_dif
string, string_pos_matrix = self._paste_char(string, string_pos_matrix, new_i + 1, char_present)
string, string_pos_matrix = self._paste_char(
string, string_pos_matrix, new_i + 1, char_present
)
i += 1
utf_16_bang_list.append((i, char_len_dif, i))
i += 1
@ -62,54 +76,70 @@ class RLStringHelper:
string.insert(pos, char)
return string, string_pos_matrix
def _delete_char(self, string: str, string_pos_matrix: list, pos: int, char_len: int, old_pos: int):
def _delete_char(
self,
string: str,
string_pos_matrix: list,
pos: int,
char_len: int,
old_pos: int,
):
string.pop(pos)
string_pos_matrix.pop(old_pos)
for matrix_i in range(pos, len(string_pos_matrix)):
if isinstance(string_pos_matrix[matrix_i], int):
string_pos_matrix[matrix_i] -= char_len
elif isinstance(string_pos_matrix[matrix_i], tuple):
string_pos_matrix[matrix_i] = (string_pos_matrix[matrix_i][0] - char_len, string_pos_matrix[matrix_i][1] - char_len)
string_pos_matrix[matrix_i] = (
string_pos_matrix[matrix_i][0] - char_len,
string_pos_matrix[matrix_i][1] - char_len,
)
return string, string_pos_matrix
@trace
def post_utf_16_bang(self, string: StringAssignmentMixin, string_pos_matrix: list, utf_16_bang_list: list):
def post_utf_16_bang(
self,
string: StringAssignmentMixin,
string_pos_matrix: list,
utf_16_bang_list: list,
):
string = StringAssignmentMixin(str(string))
post_transbang = 0
for bang_pos, char_len, old_pos in utf_16_bang_list:
string, string_pos_matrix = self._delete_char(string, string_pos_matrix, bang_pos - post_transbang, char_len, old_pos - post_transbang)
string, string_pos_matrix = self._delete_char(
string,
string_pos_matrix,
bang_pos - post_transbang,
char_len,
old_pos - post_transbang,
)
post_transbang += char_len
return string, string_pos_matrix
@trace
def set_template(self, start: int, end: int, template: str):
if not isinstance(template, Template):
template = jinja_env.from_string(template)
self.templates.append(((start, end), template))
@trace
def set_replace(self, start: int, end: int, replace_with: str):
self.replaces.append(((start, end), replace_with))
@trace
def _render_templates(self, string: str, string_pos_matrix: list, utf_16_bang_list: list):
def _render_templates(
self, string: str, string_pos_matrix: list, utf_16_bang_list: list
):
if not self.templates:
return string, string_pos_matrix, utf_16_bang_list
templates = reversed(self.templates)
updated_text = string
@trace
def _get_prefix_len(template_raw: Template, inner_char: str = "{"):
template = template_raw.render()
return template.find(inner_char)
@trace
def _get_suffix_len(template_raw: Template, outer_char: str = "}"):
template = template_raw.render()
return len(template) - template.rfind(outer_char) - 1
@trace
def update_nested_positions(start, end, prefix_len, suffix_len):
for i in range(end, len(string_pos_matrix)):
string_pos_matrix[i] += suffix_len + prefix_len
@ -118,9 +148,17 @@ class RLStringHelper:
for n in range(len(utf_16_bang_list)):
utf_16_bang = utf_16_bang_list[n]
if utf_16_bang[2] > end:
utf_16_bang_list[n] = (utf_16_bang[0] + prefix_len + suffix_len, utf_16_bang[1], utf_16_bang[2])
utf_16_bang_list[n] = (
utf_16_bang[0] + prefix_len + suffix_len,
utf_16_bang[1],
utf_16_bang[2],
)
elif utf_16_bang[2] > start:
utf_16_bang_list[n] = (utf_16_bang[0] + prefix_len, utf_16_bang[1], utf_16_bang[2])
utf_16_bang_list[n] = (
utf_16_bang[0] + prefix_len,
utf_16_bang[1],
utf_16_bang[2],
)
for (start, end), template in templates:
if start >= len(string_pos_matrix) or end - 1 >= len(string_pos_matrix):
@ -128,13 +166,23 @@ class RLStringHelper:
if start == end:
continue
new_start, new_end = string_pos_matrix[start], string_pos_matrix[end - 1] + 1
new_start, new_end = (
string_pos_matrix[start],
string_pos_matrix[end - 1] + 1,
)
if new_end < new_start:
continue
context_text = template.render(text=updated_text[new_start:new_end])
updated_text_template = jinja_env.from_string("{{ updated_text[:new_start] }}{{ context_text }}{{updated_text[new_end:]}}")
updated_text = updated_text_template.render(updated_text=updated_text, context_text=context_text, new_start=new_start, new_end=new_end)
updated_text_template = jinja_env.from_string(
"{{ updated_text[:new_start] }}{{ context_text }}{{updated_text[new_end:]}}"
)
updated_text = updated_text_template.render(
updated_text=updated_text,
context_text=context_text,
new_start=new_start,
new_end=new_end,
)
prefix_len = _get_prefix_len(template)
suffix_len = _get_suffix_len(template)
@ -142,16 +190,21 @@ class RLStringHelper:
return updated_text, string_pos_matrix, utf_16_bang_list
@trace
def _render_replaces(self, string: StringAssignmentMixin, string_pos_matrix: list, utf_16_bang_list: list):
def _render_replaces(
self,
string: StringAssignmentMixin,
string_pos_matrix: list,
utf_16_bang_list: list,
):
if not self.replaces and not self.quote_replaces:
return string, string_pos_matrix, utf_16_bang_list
string = StringAssignmentMixin(str(string))
replaces = self.replaces + self.quote_replaces
@trace
def update_positions(start: int, end: int, replace_len: int, new_start: int, new_end: int):
def update_positions(
start: int, end: int, replace_len: int, new_start: int, new_end: int
):
pos_len_diff = replace_len - (end - start)
for pos_index in range(end, len(string_pos_matrix)):
if isinstance(string_pos_matrix[pos_index], int):
@ -176,7 +229,11 @@ class RLStringHelper:
for n in range(len(utf_16_bang_list)):
utf_16_bang = utf_16_bang_list[n]
if utf_16_bang[0] > end:
utf_16_bang_list[n] = (utf_16_bang[0] + pos_len_diff, utf_16_bang[1], utf_16_bang[2])
utf_16_bang_list[n] = (
utf_16_bang[0] + pos_len_diff,
utf_16_bang[1],
utf_16_bang[2],
)
for (start, end), replace_with in replaces:
new_start, new_end = string_pos_matrix[start], string_pos_matrix[end - 1]
@ -184,7 +241,9 @@ class RLStringHelper:
new_end += 1
if isinstance(new_start, tuple) or isinstance(new_end, tuple):
new_start = min(new_start) if isinstance(new_start, tuple) else new_start
new_start = (
min(new_start) if isinstance(new_start, tuple) else new_start
)
new_end = max(new_end) if isinstance(new_end, tuple) else new_end
string[new_start:new_end] = replace_with
@ -192,22 +251,31 @@ class RLStringHelper:
return string, string_pos_matrix, utf_16_bang_list
@trace
def __str__(self):
string = StringAssignmentMixin(self.string)
string_pos_matrix = list(range(len(string)))
updated_text, string_pos_matrix, utf_16_bang_list = self.pre_utf_16_bang(string, string_pos_matrix)
updated_text, string_pos_matrix, utf_16_bang_list = self.pre_utf_16_bang(
string, string_pos_matrix
)
if self.quote_html_type:
self.quote_replaces = list(quote_html(str(updated_text), self.quote_html_type))
self.quote_replaces = list(
quote_html(str(updated_text), self.quote_html_type)
)
if not self.templates and not self.replaces and not self.quote_replaces:
return self.string
updated_text, string_pos_matrix, utf_16_bang_list = self._render_templates(updated_text, string_pos_matrix, utf_16_bang_list)
updated_text, string_pos_matrix, utf_16_bang_list = self._render_replaces(updated_text, string_pos_matrix, utf_16_bang_list)
updated_text, string_pos_matrix = self.post_utf_16_bang(updated_text, string_pos_matrix, utf_16_bang_list)
updated_text, string_pos_matrix, utf_16_bang_list = self._render_templates(
updated_text, string_pos_matrix, utf_16_bang_list
)
updated_text, string_pos_matrix, utf_16_bang_list = self._render_replaces(
updated_text, string_pos_matrix, utf_16_bang_list
)
updated_text, string_pos_matrix = self.post_utf_16_bang(
updated_text, string_pos_matrix, utf_16_bang_list
)
return str(updated_text)
def get_text(self):
@ -281,10 +349,20 @@ def parse_markups(markups: list[str]):
for markup in markups:
if markup["type"] == "A":
if markup["anchorType"] == "LINK":
template = jinja_env.from_string('<a style="text-decoration: underline;" rel="{{rel}}" title="{{title}}" href="{{href}}" target="_blank">{{text}}</a>')
template = template.render(raw_render(rel=markup.get("rel", ""), title=markup.get("title", ""), href=markup["href"]))
template = jinja_env.from_string(
'<a style="text-decoration: underline;" rel="{{rel}}" title="{{title}}" href="{{href}}" target="_blank">{{text}}</a>'
)
template = template.render(
raw_render(
rel=markup.get("rel", ""),
title=markup.get("title", ""),
href=markup["href"],
)
)
elif markup["anchorType"] == "USER":
template = jinja_env.from_string('<a style="text-decoration: underline;" href="https://medium.com/u/{{userId}}">{{text}}</a>')
template = jinja_env.from_string(
'<a style="text-decoration: underline;" href="https://medium.com/u/{{userId}}">{{text}}</a>'
)
template = template.render(userId=markup["userId"])
else:
continue
@ -293,7 +371,9 @@ def parse_markups(markups: list[str]):
elif markup["type"] == "EM":
template = "<em>{{text}}</em>"
elif markup["type"] == "CODE":
template = "<code class='p-1.5 bg-gray-300 dark:bg-gray-600'>{{text}}</code>"
template = (
"<code class='p-1.5 bg-gray-300 dark:bg-gray-600'>{{text}}</code>"
)
else:
continue

View file

@ -1,9 +0,0 @@
#!/bin/bash
# pip install nuitka==2.0.1
# sudo apt install patchelf ccache -y
# sudo /usr/sbin/update-ccache-symlinks
# export PATH="/usr/lib/ccache:$PATH"
python3 -m nuitka --standalone --nofollow-import-to=pytest --python-flag=nosite,-O,isolated --plugin-enable=anti-bloat,implicit-imports,data-files,pylint-warnings --warn-implicit-exceptions --warn-unusual-code --prefer-source-code --include-package=uvicorn.workers --include-package=sentry_sdk.integrations server

View file

@ -1,37 +0,0 @@
import asyncio
import aiohttp
import os
from aiogram import Bot
from loguru import logger
BOT_TOKEN = os.getenv("BOT_TOKEN")
if not BOT_TOKEN:
raise ValueError("No bot token!")
bot = Bot(BOT_TOKEN)
ADMIN_CHAT_ID = "1621425349"
SLEEP_TIME = 15 * 60
async def main():
while True:
logger.debug("Checking health of freedium.cfd")
try:
async with aiohttp.ClientSession() as session:
async with session.get("https://freedium.cfd", timeout=3) as response:
response_status = response.status
except Exception as ex:
logger.exception(ex)
response_status = "ERROR"
finally:
if response_status != 200:
await bot.send_message(ADMIN_CHAT_ID, "EMERGENCY! SITE IS DOWN!!!")
logger.debug("Sleeping ...")
await asyncio.sleep(SLEEP_TIME)
asyncio.run(main())

View file

@ -1,2 +0,0 @@
ruff check ./ --fix
black .

View file

@ -1,148 +0,0 @@
handle_path /site.webmanifest {
root * ./static/site.webmanifest
file_server
}
handle_path /favicon-32x32.png {
root * ./static/favicon-32x32.png
file_server
}
handle_path /robots.txt {
root * ./static/robots.txt
file_server
}
handle_path /ads.txt {
root * ./static/ads.txt
file_server
}
handle_path /humans.txt {
root * ./static/humans.txt
file_server
}
handle_path /mstile-150x150.png {
root * ./static/mstile-150x150.png
file_server
}
handle_path /mstile-310x310.png {
root * ./static/mstile-310x310.png
file_server
}
handle_path /sitemap.xml {
root * ./static/sitemap.xml
file_server
}
handle_path /99860281ef1143d5a5558ad9a21a470d.txt {
root * ./static/99860281ef1143d5a5558ad9a21a470d.txt
file_server
}
handle_path /mstile-70x70.png {
root * ./static/mstile-70x70.png
file_server
}
handle_path /android-chrome-192x192.png {
root * ./static/android-chrome-192x192.png
file_server
}
handle_path /mstile-310x150.png {
root * ./static/mstile-310x150.png
file_server
}
handle_path /safari-pinned-tab.svg {
root * ./static/safari-pinned-tab.svg
file_server
}
handle_path /android-chrome-512x512.png {
root * ./static/android-chrome-512x512.png
file_server
}
handle_path /favicon-16x16.png {
root * ./static/favicon-16x16.png
file_server
}
handle_path /favicon.ico {
root * ./static/favicon.ico
file_server
}
handle_path /browserconfig.xml {
root * ./static/browserconfig.xml
file_server
}
handle_path /mstile-144x144.png {
root * ./static/mstile-144x144.png
file_server
}
handle_path /security.txt {
root * ./static/security.txt
file_server
}
handle_path /apple-touch-icon.png {
root * ./static/apple-touch-icon.png
file_server
}
handle_path /onboarding/* {
respond "Access denied" 403
}
handle_path /wp-* {
respond "Access denied" 403
}
handle_path /.env {
respond "Access denied" 403
}
handle_path /api* {
respond "Access denied" 403
}
handle_path /apple-touch-icon-precomposed.png {
respond "Access denied" 403
}
handle_path /rss.xml {
respond "Access denied" 403
}
handle_path /.git/* {
respond "Access denied" 403
}
handle_path /apple-touch-icon-120x120.png {
respond "Access denied" 403
}
handle_path /apple-touch-icon-120x120-precomposed.png {
respond "Access denied" 403
}
handle_path /apple-touch-icon-152x152.png {
respond "Access denied" 403
}
handle_path /apple-touch-icon-152x152-precomposed.png {
respond "Access denied" 403
}
handle_path /.well-known/* {
respond "Access denied" 403
}

View file

@ -1,13 +0,0 @@
import http.server
import socketserver
class RedirectHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(301) # 301 indicates a permanent redirect
self.send_header('Location', 'https://freedium.cfd')
self.end_headers()
port = 80
server = socketserver.TCPServer(('', port), RedirectHandler)
print(f"Redirect server running at http://localhost:{port}")
server.serve_forever()

View file

@ -1,4 +0,0 @@
#!/bin/bash
arch=$(lscpu | grep Architecture | awk {'print $2'})
sudo setcap cap_net_bind_service=+ep $(pwd)/bin/${arch}/caddy

View file

@ -1,105 +0,0 @@
import os
import sys
import time
from loguru import logger
import docker
import requests
logger.remove()
logger.add(sys.stdout, level="DEBUG")
error_msg = "[Cloudflare proxy] Freedium proxy is not working, restarting containers..."
USER_NAME = os.getenv("USER_NAME")
USER_PASSWORD = os.getenv("USER_PASSWORD")
PROXY_HOST = os.getenv("PROXY_HOST", "localhost")
PROXY_PORT = os.getenv("PROXY_PORT", "9681") # 1080
_AUTH_DATA = [USER_NAME, USER_PASSWORD]
AUTH_FORMATED = ":".join(filter(bool, _AUTH_DATA))
if not PROXY_HOST or not PROXY_PORT:
raise ValueError("PROXY_HOST and PROXY POER must be set")
containers_to_restart = ["dante_1", "wgcf1"]
def restart_containers():
# client = docker.from_env()
client = docker.DockerClient(base_url="unix://var/run/docker.sock")
for container_name in containers_to_restart:
try:
container = client.containers.get(container_name)
logger.info(f"Restarting {container_name}...")
container.restart()
logger.info(f"{container_name} restarted successfully.")
except docker.errors.NotFound:
logger.error(f"Container {container_name} not found.")
except Exception as e:
logger.error(f"Error restarting {container_name}: {str(e)}")
def send_msg(text: str, max_retries: int = 3) -> bool:
for attempt in range(max_retries):
try:
tg_token = os.getenv("TG_TOKEN")
chat_id = os.getenv("TG_CHAT_ID")
url_req = "https://api.telegram.org/bot" + tg_token + "/sendMessage" + "?chat_id=" + chat_id + "&text=" + text
results = requests.get(url_req)
logger.debug(results.json())
except Exception as e:
logger.exception(e)
logger.error(f"Error sending message: {str(e)}")
if attempt < max_retries - 1:
logger.info(f"Retrying in 5 seconds...")
time.sleep(5)
else:
logger.info("Message sent successfully.")
return True
logger.error("All retry attempts failed. Message not sent.")
return False
def check_proxy(max_retries=3, retry_delay=5) -> bool:
auth_str = f"{AUTH_FORMATED}@" if AUTH_FORMATED else ""
proxy_url = f"socks5://{auth_str}{PROXY_HOST}:{PROXY_PORT}"
proxy_test_url = "http://ipinfo.io"
logger.info(f"Checking proxy: {proxy_url}")
for attempt in range(max_retries):
try:
resp = requests.get(
proxy_test_url,
proxies=dict(
http=proxy_url,
https=proxy_url,
),
timeout=5,
)
logger.info(f"Proxy is working: {resp.status_code}")
return True
except Exception as e:
logger.exception(e)
logger.error(f"Error checking proxy (attempt {attempt + 1}/{max_retries}): {str(e)}")
if attempt < max_retries - 1:
logger.info(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
logger.error("All retry attempts failed. Proxy is not working.")
return False
if __name__ == "__main__":
while True:
time.sleep(75)
# time.sleep(10)
try:
logger.info("Checking proxy...")
if not check_proxy():
logger.info(error_msg)
send_msg(error_msg)
restart_containers()
except Exception as e:
logger.error(f"Error checking proxy: {str(e)}")

View file

@ -1 +0,0 @@
from .scheduler import scheduler

View file

@ -1,3 +0,0 @@
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler = AsyncIOScheduler()

View file

@ -1,20 +0,0 @@
<div class="md:max-w-6xl bg-white dark:bg-gray-600 w-full shadow-md rounded-md p-8 mt-8">
<form id="medium-link-form" class="flex items-center border rounded-md border-gray-300 px-4 py-2">
<svg class="h-5 w-5 text-gray-500 dark:text-gray-100 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
<input id="medium-link-input"
type="text"
placeholder="Enter Medium post link"
class="w-full focus:outline-none text-green-500 dark:bg-gray-600 border-gray-300">
<button type="submit" class="ml-2 bg-green-500 text-white px-4 py-2 rounded-md hover:bg-green-600 focus:outline-none">Go</button>
</form>
</div>
<script>
document.getElementById('medium-link-form').addEventListener('submit', function (event) {
event.preventDefault();
const mediumLinkInput = document.getElementById('medium-link-input');
window.location.href = `${window.location.origin}/${mediumLinkInput.value}`;
});
</script>

View file

@ -1,39 +0,0 @@
import asyncio
import time
from functools import wraps
from loguru import logger
def trace(func):
if asyncio.iscoroutinefunction(func):
logger.trace(f"{func.__name__!r} function is a coroutine")
@wraps(func)
async def wrapper(*args, **kwargs):
start_ts = time.time()
logger.trace(f"Calling {func.__name__}() with {args}, {kwargs}")
original_result = await func(*args, **kwargs)
logger.trace(f"Result: {original_result}")
logger.trace(f"Result type: {type(original_result)}")
duration_ts = time.time() - start_ts
result = f"{original_result[:42]}..." if type(original_result).__name__ in ["str", "bytes"] else original_result
logger.trace(f"{func.__name__!r}() returned {result!r} in {duration_ts:.2} seconds")
return original_result
else:
logger.trace(f"{func.__name__!r} is not a coroutine")
@wraps(func)
def wrapper(*args, **kwargs):
start_ts = time.time()
logger.trace(f"Calling {func.__name__}() with {args}, {kwargs}")
original_result = func(*args, **kwargs)
logger.trace(f"Result: {original_result}")
logger.trace(f"Result type: {type(original_result)}")
duration_ts = time.time() - start_ts
result = f"{original_result[:42]}..." if type(original_result).__name__ in ["str", "bytes"] else original_result
logger.trace(f"{func.__name__!r}() returned {result!r} in {duration_ts:.2} seconds")
return original_result
return wrapper

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "annotated-types"
@ -32,32 +32,48 @@ test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=
trio = ["trio (>=0.22)"]
[[package]]
name = "apscheduler"
version = "3.10.4"
description = "In-process task scheduler with Cron-like capabilities"
name = "black"
version = "24.8.0"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.6"
python-versions = ">=3.8"
files = [
{file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"},
{file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"},
{file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"},
{file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"},
{file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"},
{file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"},
{file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"},
{file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"},
{file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"},
{file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"},
{file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"},
{file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"},
{file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"},
{file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"},
{file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"},
{file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"},
{file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"},
{file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"},
{file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"},
{file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"},
{file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"},
{file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"},
{file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"},
{file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"},
]
[package.dependencies]
pytz = "*"
six = ">=1.4.0"
tzlocal = ">=2.0,<3.dev0 || >=4.dev0"
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
[package.extras]
doc = ["sphinx", "sphinx-rtd-theme"]
gevent = ["gevent"]
mongodb = ["pymongo (>=3.0)"]
redis = ["redis (>=3.0)"]
rethinkdb = ["rethinkdb (>=2.4.0)"]
sqlalchemy = ["sqlalchemy (>=1.4)"]
testing = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-tornado5"]
tornado = ["tornado (>=4.3)"]
twisted = ["twisted"]
zookeeper = ["kazoo"]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
@ -95,6 +111,55 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "cssbeautifier"
version = "1.15.1"
description = "CSS unobfuscator and beautifier."
optional = false
python-versions = "*"
files = [
{file = "cssbeautifier-1.15.1.tar.gz", hash = "sha256:9f7064362aedd559c55eeecf6b6bed65e05f33488dcbe39044f0403c26e1c006"},
]
[package.dependencies]
editorconfig = ">=0.12.2"
jsbeautifier = "*"
six = ">=1.13.0"
[[package]]
name = "djlint"
version = "1.35.2"
description = "HTML Template Linter and Formatter"
optional = false
python-versions = "<4.0,>=3.8"
files = [
{file = "djlint-1.35.2-py3-none-any.whl", hash = "sha256:4ba995bad378f2afa77c8ea56ba1c14429d9ff26a18e8ae23bc71eedb9152243"},
{file = "djlint-1.35.2.tar.gz", hash = "sha256:318de9d4b9b0061a111f8f5164ecbacd8215f449dd4bd5a76d2a691c815ee103"},
]
[package.dependencies]
click = ">=8.0.1"
colorama = ">=0.4.4"
cssbeautifier = ">=1.14.4"
html-tag-names = ">=0.1.2"
html-void-elements = ">=0.1.0"
jsbeautifier = ">=1.14.4"
json5 = ">=0.9.11"
pathspec = ">=0.12.0"
PyYAML = ">=6.0"
regex = ">=2023"
tqdm = ">=4.62.2"
[[package]]
name = "editorconfig"
version = "0.12.4"
description = "EditorConfig File Locator and Interpreter for Python"
optional = false
python-versions = "*"
files = [
{file = "EditorConfig-0.12.4.tar.gz", hash = "sha256:24857fa1793917dd9ccf0c7810a07e05404ce9b823521c7dce22a4fb5d125f80"},
]
[[package]]
name = "fastapi"
version = "0.108.0"
@ -248,6 +313,28 @@ files = [
{file = "hiredis-3.0.0.tar.gz", hash = "sha256:fed8581ae26345dea1f1e0d1a96e05041a727a45e7d8d459164583e23c6ac441"},
]
[[package]]
name = "html-tag-names"
version = "0.1.2"
description = "List of known HTML tag names"
optional = false
python-versions = ">=3.7,<4.0"
files = [
{file = "html-tag-names-0.1.2.tar.gz", hash = "sha256:04924aca48770f36b5a41c27e4d917062507be05118acb0ba869c97389084297"},
{file = "html_tag_names-0.1.2-py3-none-any.whl", hash = "sha256:eeb69ef21078486b615241f0393a72b41352c5219ee648e7c61f5632d26f0420"},
]
[[package]]
name = "html-void-elements"
version = "0.1.0"
description = "List of HTML void tag names."
optional = false
python-versions = ">=3.7,<4.0"
files = [
{file = "html-void-elements-0.1.0.tar.gz", hash = "sha256:931b88f84cd606fee0b582c28fcd00e41d7149421fb673e1e1abd2f0c4f231f0"},
{file = "html_void_elements-0.1.0-py3-none-any.whl", hash = "sha256:784cf39db03cdeb017320d9301009f8f3480f9d7b254d0974272e80e0cb5e0d2"},
]
[[package]]
name = "html5lib"
version = "1.1"
@ -297,6 +384,31 @@ MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "jsbeautifier"
version = "1.15.1"
description = "JavaScript unobfuscator and beautifier."
optional = false
python-versions = "*"
files = [
{file = "jsbeautifier-1.15.1.tar.gz", hash = "sha256:ebd733b560704c602d744eafc839db60a1ee9326e30a2a80c4adb8718adc1b24"},
]
[package.dependencies]
editorconfig = ">=0.12.2"
six = ">=1.13.0"
[[package]]
name = "json5"
version = "0.9.25"
description = "A Python implementation of the JSON5 data format."
optional = false
python-versions = ">=3.8"
files = [
{file = "json5-0.9.25-py3-none-any.whl", hash = "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f"},
{file = "json5-0.9.25.tar.gz", hash = "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae"},
]
[[package]]
name = "loguru"
version = "0.6.0"
@ -384,6 +496,17 @@ files = [
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "packaging"
version = "24.1"
@ -395,6 +518,17 @@ files = [
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
]
[[package]]
name = "pathspec"
version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
name = "pickledb"
version = "0.9.2"
@ -405,6 +539,22 @@ files = [
{file = "pickleDB-0.9.2.tar.gz", hash = "sha256:ec6973e65d7d112849e78ce522840aa908efb2523470bb8ce5c7942310192240"},
]
[[package]]
name = "platformdirs"
version = "4.3.6"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.8"
files = [
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
]
[package.extras]
docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
type = ["mypy (>=1.11.2)"]
[[package]]
name = "pydantic"
version = "2.8.2"
@ -529,14 +679,65 @@ files = [
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pytz"
version = "2024.1"
description = "World timezone definitions, modern and historical"
name = "pyyaml"
version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = false
python-versions = "*"
python-versions = ">=3.8"
files = [
{file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"},
{file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
]
[[package]]
@ -557,6 +758,136 @@ hiredis = {version = ">=1.0.0", optional = true, markers = "extra == \"hiredis\"
hiredis = ["hiredis (>=1.0.0)"]
ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"]
[[package]]
name = "regex"
version = "2024.9.11"
description = "Alternative regular expression module, to replace re."
optional = false
python-versions = ">=3.8"
files = [
{file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"},
{file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"},
{file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"},
{file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"},
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"},
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"},
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"},
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"},
{file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"},
{file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"},
{file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"},
{file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"},
{file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"},
{file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"},
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"},
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"},
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"},
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"},
{file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"},
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"},
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"},
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"},
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"},
{file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"},
{file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"},
{file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"},
{file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"},
{file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"},
{file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"},
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"},
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"},
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"},
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"},
{file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"},
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"},
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"},
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"},
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"},
{file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"},
{file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"},
{file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"},
{file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"},
{file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"},
{file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"},
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"},
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"},
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"},
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"},
{file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"},
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"},
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"},
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"},
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"},
{file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"},
{file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"},
{file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"},
{file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"},
{file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"},
{file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"},
{file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"},
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"},
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"},
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"},
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"},
{file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"},
{file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"},
{file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"},
{file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"},
{file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"},
{file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"},
{file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"},
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"},
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"},
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"},
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"},
{file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"},
{file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"},
{file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"},
{file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"},
]
[[package]]
name = "ruff"
version = "0.6.6"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.6.6-py3-none-linux_armv6l.whl", hash = "sha256:f5bc5398457484fc0374425b43b030e4668ed4d2da8ee7fdda0e926c9f11ccfb"},
{file = "ruff-0.6.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:515a698254c9c47bb84335281a170213b3ee5eb47feebe903e1be10087a167ce"},
{file = "ruff-0.6.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6bb1b4995775f1837ab70f26698dd73852bbb82e8f70b175d2713c0354fe9182"},
{file = "ruff-0.6.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c546f412dfae8bb9cc4f27f0e45cdd554e42fecbb34f03312b93368e1cd0a6"},
{file = "ruff-0.6.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59627e97364329e4eae7d86fa7980c10e2b129e2293d25c478ebcb861b3e3fd6"},
{file = "ruff-0.6.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94c3f78c3d32190aafbb6bc5410c96cfed0a88aadb49c3f852bbc2aa9783a7d8"},
{file = "ruff-0.6.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:704da526c1e137f38c8a067a4a975fe6834b9f8ba7dbc5fd7503d58148851b8f"},
{file = "ruff-0.6.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efeede5815a24104579a0f6320660536c5ffc1c91ae94f8c65659af915fb9de9"},
{file = "ruff-0.6.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e368aef0cc02ca3593eae2fb8186b81c9c2b3f39acaaa1108eb6b4d04617e61f"},
{file = "ruff-0.6.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2653fc3b2a9315bd809725c88dd2446550099728d077a04191febb5ea79a4f79"},
{file = "ruff-0.6.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bb858cd9ce2d062503337c5b9784d7b583bcf9d1a43c4df6ccb5eab774fbafcb"},
{file = "ruff-0.6.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:488f8e15c01ea9afb8c0ba35d55bd951f484d0c1b7c5fd746ce3c47ccdedce68"},
{file = "ruff-0.6.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:aefb0bd15f1cfa4c9c227b6120573bb3d6c4ee3b29fb54a5ad58f03859bc43c6"},
{file = "ruff-0.6.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a4c0698cc780bcb2c61496cbd56b6a3ac0ad858c966652f7dbf4ceb029252fbe"},
{file = "ruff-0.6.6-py3-none-win32.whl", hash = "sha256:aadf81ddc8ab5b62da7aae78a91ec933cbae9f8f1663ec0325dae2c364e4ad84"},
{file = "ruff-0.6.6-py3-none-win_amd64.whl", hash = "sha256:0adb801771bc1f1b8cf4e0a6fdc30776e7c1894810ff3b344e50da82ef50eeb1"},
{file = "ruff-0.6.6-py3-none-win_arm64.whl", hash = "sha256:4b4d32c137bc781c298964dd4e52f07d6f7d57c03eae97a72d97856844aa510a"},
{file = "ruff-0.6.6.tar.gz", hash = "sha256:0fc030b6fd14814d69ac0196396f6761921bd20831725c7361e1b8100b818034"},
]
[[package]]
name = "sentry-sdk"
version = "1.29.2"
@ -639,6 +970,26 @@ anyio = ">=3.4.0,<5"
[package.extras]
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"]
[[package]]
name = "tqdm"
version = "4.66.5"
description = "Fast, Extensible Progress Meter"
optional = false
python-versions = ">=3.7"
files = [
{file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"},
{file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[package.extras]
dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"]
notebook = ["ipywidgets (>=6)"]
slack = ["slack-sdk"]
telegram = ["requests"]
[[package]]
name = "typing-extensions"
version = "4.12.2"
@ -650,34 +1001,6 @@ files = [
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "tzdata"
version = "2024.1"
description = "Provider of IANA time zone data"
optional = false
python-versions = ">=2"
files = [
{file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"},
{file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
]
[[package]]
name = "tzlocal"
version = "5.2"
description = "tzinfo object for the local timezone"
optional = false
python-versions = ">=3.8"
files = [
{file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"},
{file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"},
]
[package.dependencies]
tzdata = {version = "*", markers = "platform_system == \"Windows\""}
[package.extras]
devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"]
[[package]]
name = "urllib3"
version = "2.2.2"
@ -751,4 +1074,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "c04733f92b42c7d2b4b8b89e8bb4510c7de5048a678b7ccc58969323a072a10f"
content-hash = "4015e3dbdfe37ed67baac4c18550b38bd93664462c449a8a0164134eb5497229"

View file

@ -19,11 +19,15 @@ fastapi = "0.108.0"
gunicorn = "21.2.0"
redis = {version = "4.6.0", extras = ["hiredis"]}
xkcdpass = "1.19.3"
apscheduler = "3.10.4"
loguru = "0.6.0"
anyio = "<=4.0.0"
[tool.poetry.group.dev.dependencies]
djlint = "^1.35.2"
ruff = "^0.6.6"
black = "^24.8.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View file

@ -43,9 +43,9 @@ wait_for_postgres()
# logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)
configure_logger()
medium_cache = PostgreSQLCacheBackend("postgresql://postgres:postgres@postgres:5432/postgres")
medium_cache = PostgreSQLCacheBackend(config.DATABASE_URL)
medium_cache.init_db()
# migrate_to_postgres_thread = execute_migrate_to_postgres_in_thread("medium_db_cache.sqlite", "postgresql://postgres:postgres@postgres:5432/postgres")
logger.debug(f"Database length: {medium_cache.all_length()}")
medium_api = MediumApi(auth_cookies=config.MEDIUM_AUTH_COOKIES, timeout=3, proxy_list=config.PROXY_LIST)

View file

@ -30,9 +30,11 @@ REDIS_HOST = config("REDIS_HOST", default="redis_service")
REDIS_PORT = config("REDIS_PORT", cast=int, default=6379)
REDIS_TIMEOUT = config("REDIS_TIMEOUT", cast=int, default=1.75)
DATABASE_URL = config("DATABASE_URL", default="postgresql://postgres:postgres@postgres:5432/postgres")
SENTRY_SDK_DSN = config("SENTRY_SDK_DSN", default=None)
SENTRY_TRACES_SAMPLE_RATE = config("SENTRY_TRACES_SAMPLE_RATE", cast=float, default=0.2)
SENTRY_PROFILES_SAMPLE_RATE = config("SENTRY_PROFILES_SAMPLE_RATE", cast=float, default=0.2)
_PROXY_LIST = config("PROXY_LIST", cast=str, default="")
PROXY_LIST = _PROXY_LIST.split(",") if _PROXY_LIST else []
PROXY_LIST_RAW = config("PROXY_LIST", cast=str, default="")
PROXY_LIST = PROXY_LIST_RAW.split(",") if PROXY_LIST_RAW else []

View file

@ -0,0 +1,44 @@
<div
class="md:max-w-6xl bg-white dark:bg-gray-600 w-full shadow-md rounded-md p-8 mt-8"
>
<form
id="medium-link-form"
class="flex items-center border rounded-md border-gray-300 px-4 py-2"
>
<svg
class="h-5 w-5 text-gray-500 dark:text-gray-100 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
></path>
</svg>
<input
id="medium-link-input"
type="text"
placeholder="Enter Medium post link"
class="w-full focus:outline-none text-green-500 dark:bg-gray-600 border-gray-300"
/>
<button
type="submit"
class="ml-2 bg-green-500 text-white px-4 py-2 rounded-md hover:bg-green-600 focus:outline-none"
>
Go
</button>
</form>
</div>
<script>
document
.getElementById("medium-link-form")
.addEventListener("submit", function (event) {
event.preventDefault();
const mediumLinkInput = document.getElementById("medium-link-input");
window.location.href = `${window.location.origin}/${mediumLinkInput.value}`;
});
</script>

View file

View file

@ -12,7 +12,6 @@ RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /main ./main.go
FROM alpine:3.20
WORKDIR /app
# COPY .env .
COPY --from=builder /main .
ENTRYPOINT ["/app/main"]

Some files were not shown because too many files have changed in this diff Show more