524 lines
36 KiB
Python
524 lines
36 KiB
Python
import mimetypes
|
||
import re
|
||
from functools import lru_cache
|
||
from types import SimpleNamespace
|
||
from urllib.parse import urlparse, parse_qs
|
||
from app.cache import time_cache
|
||
import emoji
|
||
import requests
|
||
from bs4 import BeautifulSoup
|
||
from jinja2 import TemplateSyntaxError, nodes
|
||
from jinja2.ext import Extension
|
||
from jinja2.nodes import Const
|
||
import bleach
|
||
|
||
|
||
emoji.EMOJI_DATA['<img src="/emoji/snek1.gif" />'] = {
|
||
"en": ":snek1:",
|
||
"status": 2,
|
||
"E": 0.6,
|
||
"alias": [":snek1:"],
|
||
}
|
||
|
||
emoji.EMOJI_DATA[
|
||
"""⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣠⣤⡴⠶⢶⣞⣛⣛⡳⣳⠶⣶⡶⢶⢶⣦⣤⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⠶⠚⣫⣴⣬⠐⣶⣿⣿⣏⣽⣿⣿⣇⢿⣯⣿⣿⣻⣿⣿⣾⣮⣹⣿⢶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⢾⣻⣽⣾⡇⢡⣿⣿⣇⡟⣿⣿⣿⣼⣿⣿⣿⣿⢸⣿⣟⣿⣷⢻⣿⣿⣿⣷⡽⣧⣹⡻⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠟⣫⣶⣿⣟⣾⡿⠀⣾⣿⣿⢹⢿⣿⣿⣏⣿⣿⣿⣿⣿⣺⣿⡏⣿⣿⣏⣿⣿⣿⣿⣿⣞⣿⣿⣯⡻⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⣠⡴⣛⣵⣻⣟⣿⢯⣿⢟⡆⣀⣿⣿⣿⣿⣾⣿⣿⢹⣿⣿⣿⣿⣿⢸⣿⣿⣿⣿⣿⣸⣿⣿⣿⣿⣿⡾⣿⣻⣿⣮⠻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡀⠀⠀⠀⢀⣠⡾⣻⣾⡿⣳⡟⣾⢿⣿⢯⣿⡇⢸⣿⣿⣿⣇⣿⣿⡿⣿⣿⣿⣿⣿⣿⣾⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⢻⣏⣿⣿⡵⣝⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣟⢧⣤⣶⢾⣻⣿⣾⣿⣿⢳⣿⢻⣏⣿⢿⣿⡿⠀⣾⣿⣿⣿⢹⣿⣿⣗⣿⣿⣿⣿⣿⢏⣿⣿⠋⣿⣿⡟⡈⣿⣿⣿⠸⣿⣿⣹⣿⢸⣿⣧⢻⣮⢿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⠿⠿⠿⢿⡟⣿⣿⣿⢧⣿⣟⡟⣾⣯⣿⡿⣽⡇⣿⣿⣿⣿⣸⣿⣿⠹⣿⡿⢻⡿⢣⣿⡿⣳⣖⣿⣿⢱⣿⢹⣿⣿⠀⣿⣿⠘⣿⡞⣿⣿⡸⣿⣯⢿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣼⣿⣿⡿⣼⣿⣿⢧⣿⣼⣿⣻⣿⡃⣿⣿⣿⣿⣿⣿⣿⣀⣿⢧⠘⣣⢸⡿⣻⣿⣿⡘⠟⣿⣿⡘⠿⠟⣟⣻⡿⣀⢿⡇⢻⣿⡇⣿⣿⢸⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣏⣿⣿⣿⢳⣿⣿⣿⣸⢷⣿⢷⣿⢟⢨⣿⣿⣿⣿⣿⣿⣯⣿⣦⠖⠘⡛⣡⠬⠭⠭⠛⠂⠰⣿⣿⡇⠀⣰⡏⢛⣴⣿⡏⢸⣼⡿⠇⣿⣿⣧⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣹⣿⣿⡏⣿⣿⣿⡇⣿⣾⣯⣿⣯⡿⢸⣿⣿⣿⣿⣿⢿⡃⣽⣶⣿⣿⣿⣿⣿⣿⣿⣷⣤⣗⠊⣻⣿⣶⣿⣶⣿⣿⣿⡇⣾⠏⡁⠀⣿⣿⣧⣹⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣧⣿⣿⣿⣹⣿⣿⣿⡇⣿⡿⣾⠳⠋⣄⣼⣿⣿⣿⣻⣾⣿⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣫⣤⣄⣀⣿⠇⣿⣿⣿⣻⡌⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣸⣿⣿⣏⣿⣿⣿⣿⣿⣿⣷⣇⢀⣾⡋⣽⣿⣿⣿⣿⣿⣿⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣱⣿⣿⣿⣿⣿⡇⣬⣻⣿⣽⡇⠸⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣇⣿⣿⣿⣹⣿⣿⢏⣅⢀⠲⣤⣙⠼⢿⡇⣿⣿⣿⣿⣿⡿⠿⠻⠟⠻⠿⠿⣷⣽⡻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣵⣿⣿⣿⣿⣿⣿⡇⣿⣧⣿⢾⡇⠀⢿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⢸⣿⣿⡿⣾⣿⣏⣾⠇⠈⠀⠈⢻⣿⣷⡁⣿⣿⣿⣿⣿⣧⣤⣴⣶⠀⠀⠀⠀⣊⡙⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⣻⣿⡇⣿⣿⣿⣻⡇⠀⢸⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡏⣿⣿⣿⡗⣿⣿⢸⡟⣼⣀⠏⠁⡀⢿⣿⡇⣹⣿⣿⣿⣿⣿⡇⢿⣯⠀⡄⠀⠁⠉⠙⣿⣮⡝⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠁⠀⣀⣀⡀⠁⢿⣻⣿⣿⡇⠀⠈⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⢣⣿⣿⣿⢸⣿⣏⢾⡧⡿⠋⠉⢁⣡⣿⣿⣧⣿⣿⣿⣿⣿⣿⣃⣷⣝⣀⣽⣤⣧⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⣀⠀⠀⠉⢩⣿⡇⣶⣳⣿⣽⡇⠀⢠⡿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣸⣿⣿⡿⣼⣿⣷⠸⠃⢻⡜⢿⣿⢟⣿⣿⣿⣿⣿⣿⣿⣿⣿⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣷⣥⣤⣥⣤⣿⣿⡅⣿⣿⣿⣿⡇⠀⣼⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⣿⣿⣿⣿⣿⣿⣿⣄⢷⣄⣉⠮⠉⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⢼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⢹⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⡇⢼⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⢧⣿⣿⣿⣿⣿⣿⣿⣿⣮⢿⣿⣿⣷⣦⡈⢿⣿⣿⣿⣿⣿⣿⣿⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢇⣿⣿⣿⣿⣿⣿⣿⣇⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣵⡝⢿⣿⣿⣿⣶⣍⣿⣿⣿⣿⣿⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢉⣾⣿⣿⣿⣿⣿⣿⣿⢹⣿⣿⣿⢿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⡟⣾⣿⣿⣿⣿⣿⣿⣿⣿⡟⣿⢻⢳⢸⢩⣟⣛⠓⣿⣿⣿⣿⣿⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⡿⢸⣿⣿⣿⣼⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⣿⣿⣿⣿⣿⠇⣿⣿⣿⠇⣿⢸⢸⣸⡇⢸⣿⠇⣿⣿⣿⣿⣿⣿⢹⣿⣯⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⢿⢿⠛⢻⣿⣿⣿⣿⣿⣿⣿⠃⢸⣿⣿⣿⢾⡃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣷⣿⣿⣿⣿⣿⠀⣿⣻⣿⢹⣿⣸⠘⡿⡇⢸⣿⡄⢿⣿⣿⣿⣿⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢛⣵⣶⣶⣬⠀⢠⣿⣿⣿⣿⣿⣿⣿⠏⢄⣸⣿⣿⣿⣿⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⢸⣿⣿⣿⣿⡟⢸⡿⣿⣿⢸⡇⡖⡄⠇⣇⢸⣿⡓⢻⣿⣿⣿⣿⣿⢺⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⣿⢧⣿⣿⣿⣿⣿⣿⣿⠋⢨⠀⣿⣿⣿⣏⣿⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡏⣾⣿⣿⣿⣿⠃⣼⣷⣿⡏⣿⣿⠇⢹⢸⣿⢺⣿⣿⢸⣿⣿⣿⣿⣿⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⣙⡛⣛⢡⣾⣿⠻⢿⣿⣿⣟⣱⡆⠨⠀⣿⣿⢿⡗⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣹⣿⣿⣿⣿⣿⠀⣼⣹⣿⢣⣿⣿⢠⣸⣼⣿⡞⣿⣿⣾⣿⣿⣿⣿⣿⢸⣦⣟⢿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣾⢿⣿⣿⠈⢷⡝⢻⡊⠁⣧⠀⠃⣿⣿⡆⢋⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⢯⣿⣿⣿⣿⣿⣿⡄⣿⣿⣿⣸⣿⣿⣸⢻⣿⣿⡗⣿⣿⡏⣿⣿⣿⣿⣿⠈⣿⣿⣬⣙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣿⡆⢨⣁⣘⣷⣄⣿⠀⢠⣿⣿⣣⣈⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣟⣿⣿⣿⣿⣿⣿⣿⢀⠯⣿⡏⣿⣿⡇⢻⣽⣿⣿⣟⣻⣿⡇⣿⣿⡏⣿⣿⣰⡼⢺⡾⣏⣿⣨⡟⠿⣿⣿⣿⣿⣿⣿⣿⢟⡉⡆⡎⠿⣿⣿⣮⡻⡇⢸⣿⣏⢿⣿⣞⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣟⣾⣿⣿⢇⣾⣿⣿⢋⣠⢰⣿⢿⣿⣿⢰⢸⢹⣿⣿⣿⣸⣿⣧⣿⣿⡇⣿⣿⢼⣿⣦⡹⢹⡜⣇⢿⣹⣶⣍⡛⠻⠿⣫⡖⡟⣠⣱⣧⣿⣞⣻⣿⣿⣦⡸⣿⣿⡜⣿⣿⣯⢿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⢟⣾⣿⣿⢏⣾⣿⣿⡏⣾⡏⢸⣿⣾⣿⡇⠆⣿⢸⣿⣿⣿⡏⣿⣷⢹⣿⣷⣿⣿⢸⣿⣿⣿⣦⡻⡸⡞⣧⢿⠧⣇⠙⠿⣿⣼⢣⠃⢋⠃⣿⠈⠳⣝⢿⣿⣿⣮⣝⢿⡼⣿⣿⣷⡩⣽⣻⢶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⢯⣿⣿⡿⣳⣿⣿⣿⡿⢸⣿⠇⣺⣿⢯⣿⡝⣸⡿⡘⣽⣿⣿⣗⢿⣿⢸⣿⣿⣿⣿⠦⣿⣿⣿⣿⣿⣶⣭⣘⠎⠘⢣⣷⡆⠂⠥⠁⣂⡟⠟⢿⣀⠀⠉⠳⣝⢿⣿⣿⣿⣦⣙⣿⣿⣿⣎⣿⣷⣝⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⣵⣿⣿⣏⢼⣿⣽⣿⡿⣁⣿⡿⠸⠹⢫⣿⣟⣼⣿⠃⣵⣿⡿⢸⣿⠘⣿⠘⣿⡏⣿⣿⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⢲⣸⡿⣳⣮⣝⡿⣿⣿⣿⣾⣿⣿⣿⣶⣿⣷⡎⠻⣿⣿⣿⣿⣷⣿⡻⢸⡿⣿⣿⣽⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⢟⣼⡿⣯⡿⣯⣿⢳⣿⣿⠗⡋⠁⠀⠀⠈⠒⢦⣝⠻⣷⣿⡿⢟⡥⣼⣿⠀⠹⢀⣿⡿⣿⣿⡏⢹⣿⣿⣿⣿⣿⣿⣿⣿⠸⢺⡇⡇⣿⢸⣿⡆⣭⣛⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣾⣿⣿⣿⣿⡼⡇⣿⣿⣿⣯⢷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡾⣣⡿⢋⡾⠋⣼⢳⣿⢿⠟⠡⢈⣥⠶⠟⠛⠿⠷⣦⣽⣷⣤⡐⢾⣿⣷⣿⢏⣀⡀⠀⣿⣷⣿⣿⣿⢨⢿⣿⣿⣿⣿⣿⣿⣿⢸⠾⡧⣿⢿⣸⣿⣶⢸⣿⣿⡗⠶⢯⣭⣛⡻⡿⠿⢿⣿⣿⣿⣭⣝⣻⡇⣻⣿⣻⢿⣿⣧⠙⢷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣟⣽⢟⣵⠟⠀⢰⡿⣾⡿⢉⣶⡾⠋⠁⠀⠀⠀⠀⠀⠀⠉⠙⠿⣿⣦⠈⠉⠠⠀⠻⣿⠀⣿⣿⣿⣿⣿⣺⣟⣿⣿⣿⣿⣿⣿⡇⢸⣶⡟⢿⡼⠇⠿⣱⠘⣿⣧⣷⠀⠀⢀⣩⡇⠿⣟⣃⣬⣿⣿⣿⣿⣿⡇⣿⢸⣿⣷⣽⣿⣯⡨⠻⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⢿⣾⣷⠟⠁⠀⠀⣾⢿⣿⠃⡞⣩⣶⣿⣿⣿⣿⣷⣦⡄⠀⠀⠀⠀⣠⣼⣷⡀⠀⢣⠀⠘⠃⣿⣿⣿⣿⣿⣏⣿⣞⣿⣿⣿⣿⣿⡇⠀⠈⠑⠾⢧⣾⡞⣵⣷⢻⣿⠻⣶⣟⣿⣽⣶⣿⣿⣿⣿⣿⣿⣿⣽⣿⡇⣿⠈⣿⣿⣿⣿⣻⣧⢱⣜⢿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⡿⠁⠀⠀⠀⢸⡟⣿⠇⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⣴⣿⣿⣿⣿⣄⠀⣃⠀⠀⣿⣿⢹⣿⣿⣇⢿⣿⣞⢿⣿⣿⣿⡇⢠⣤⣤⣄⡀⢤⣬⠁⠈⠁⣿⣾⣿⣿⡿⢿⣻⢛⣭⣿⢏⣼⣿⣿⣿⣿⠇⣮⣤⣿⣿⣿⣿⣿⣿⣇⢿⣎⢿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⠋⠀⠀⠀⠀⢀⣿⢻⡟⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀⣼⣿⣿⣿⣿⣿⣿⣦⠘⡇⡀⣿⣿⢸⣿⣿⣿⠸⣿⣿⣾⣿⣿⣿⡇⢰⣬⣝⡻⢿⣇⣂⣬⣤⣙⡋⠭⢥⣀⣀⠈⠙⠃⣫⣷⣿⣿⣿⣿⣭⣿⢢⣿⣿⣿⣿⣿⣿⣿⣿⣿⠈⣿⣎⢿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡟⣾⢁⣾⣿⠉⠙⣿⣿⣿⣿⣿⣿⡿⠁⠀⣼⣿⣿⣿⣿⡿⠿⣿⣿⡇⢁⣿⢹⣿⣾⣿⣿⣿⡄⣜⢿⣿⣿⣿⣿⣿⠀⠻⣿⣿⣶⣍⠻⣿⣿⣿⣿⡿⢃⣾⠏⣠⣴⣿⣿⡿⠿⣛⢩⡜⢿⢏⢼⣿⣿⣿⣿⣿⣾⢿⣿⣿⠀⣿⢻⣎⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⢹⡟⣸⣿⡇⠀⠀⠈⢿⣿⣿⡿⠋⠀⠀⢠⣿⡿⠟⠉⠁⣠⡾⢋⣿⣧⠘⠻⢸⣿⣏⣿⣿⣿⣧⢉⣓⣈⢛⡿⠿⣿⣦⣶⣿⣿⣿⠿⣂⣤⡽⣿⠏⣴⡿⢋⣾⡿⠟⠋⠐⣾⣿⣿⡄⠻⣶⢿⣣⣮⣝⢿⣿⣿⣿⣷⡻⣿⠀⡟⠈⣿⡜⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⡏⣿⡇⣿⣿⠃⠀⠀⠀⠀⠙⠉⠀⠀⠀⠀⠘⠉⠀⣀⣴⡾⢋⣵⣿⣿⣧⡄⠀⢸⣿⣿⣿⣿⣿⣿⡄⠙⠛⢓⣙⢿⣷⣾⣿⣿⣿⣵⣾⣿⣿⣧⣄⣸⠋⢤⡬⠥⠀⠀⢶⡄⠈⠹⣿⣇⢀⠈⢝⣿⣿⣿⣿⡽⣿⣿⣿⡿⠁⢠⡇⠀⢻⣿⣹⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡿⣸⣿⣷⠸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⠟⣩⣶⣿⣿⣿⣿⣿⣿⠀⢸⣿⣿⣿⣿⣿⣿⣧⠸⠛⣛⣯⣤⣿⡻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣔⢶⣶⣷⡀⢻⣄⠀⠙⠿⣎⠀⠈⠻⣿⣿⣿⣿⣟⣿⢿⠁⠀⣼⠀⠀⠘⣿⣧⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⢣⣿⣿⣿⡇⠃⠀⠀⠀⠀⠀⠀⠀⣀⣤⣾⣿⡿⢛⣥⣾⣿⣿⣿⣿⣿⣿⣿⡟⠀⢸⣿⣿⣿⣿⣿⣿⣿⡄⢶⣭⣙⡛⠿⢿⣾⣝⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡻⢿⣀⠙⠂⠀⣀⠈⠑⠀⠀⠀⢠⣮⠍⡉⢠⡌⠁⠀⠇⢀⣤⡀⢻⡟⢏⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣏⣿⣿⣿⣿⣠⠈⠀⠀⢀⣠⣴⣾⣿⣿⣿⠟⣋⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠁⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⡀⠉⡛⢿⣷⣦⣬⣉⠓⢮⡻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣝⠦⠀⠂⠙⣷⣄⡀⠀⠀⠀⠀⠀⠐⠀⠀⠀⣶⠀⣸⣿⡇⠸⣇⠘⡜⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡟⣼⣿⣿⣿⡏⣿⢰⣶⣿⠛⠛⠛⠉⠉⠁⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⢻⣿⣧⠀⢹⣷⣮⠛⣿⣿⣿⣦⡌⠐⡝⢻⣿⣿⣿⣶⣽⣿⣿⣿⣿⣿⣷⡄⠀⢠⢹⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⣼⠃⠀⠘⣿⠃⠀⣭⠀⢻⠙⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⢳⣿⣿⣿⣿⣷⣿⡆⣿⠃⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⢠⡇⢰⠂⣿⣿⣿⣿⣿⣿⡈⢿⣿⡄⠈⣿⣿⣷⣤⡉⠛⠿⣿⣷⣦⣔⡙⠻⣿⣿⣿⣿⣝⢿⣿⣿⣿⣿⣦⡀⢈⢻⣿⣿⣿⣄⢤⣀⠀⢀⣴⠏⠀⠀⠀⠀⠀⣾⣿⡇⢸⣧⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⢯⣿⣿⣿⣿⣿⣿⣿⡇⣇⢀⣀⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⣡⠄⠀⣼⡇⢺⠁⣿⣿⣧⣿⣿⣿⡇⠈⢿⣿⡀⢹⣿⣿⣿⣿⣯⣻⣶⣯⣽⣿⠿⣿⣾⣽⣻⢿⣿⣿⣽⣿⢿⣿⣿⣿⣞⣇⢙⢿⣿⣿⣯⣻⣿⣶⣤⣀⠀⠀⠀⠀⠀⢻⡿⠁⠈⣿⣧⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣏⣿⣿⣿⣿⢿⣿⣿⡿⢃⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⠁⢹⣿⣿⣿⣿⡟⠀⣸⣿⡇⠈⠀⣿⣿⣿⣿⡏⣿⣧⠀⠀⠻⣷⡜⣿⣿⣿⣿⣿⣿⣷⣽⡻⣿⣿⣷⣿⣽⣛⡿⢾⣽⣛⢿⣿⣾⣝⡿⣿⣯⡃⢣⡻⣿⣿⣷⡽⣿⣿⣿⣷⣦⣀⠀⠀⠀⠀⠀⠀⢿⣿⣧⢻⡄⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣟⣿⣿⢯⣿⢏⣾⣿⣿⢃⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠀⣠⣿⣿⣿⣿⣿⠀⣸⣿⣿⡇⠀⠀⣿⣿⢹⣿⡇⢻⣿⠀⠀⢠⡌⠿⣜⢿⣿⣿⣿⣿⣿⣿⣿⣾⣝⡿⣿⣿⣿⣿⣿⣶⣯⣝⣊⠽⢛⠿⣷⣝⢷⣤⠳⡜⢿⣿⣿⣝⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⢸⣿⣿⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⡟⣾⣿⣯⣿⢣⣾⣿⣿⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⣠⣼⣿⣿⣿⣿⣿⠃⣰⣿⣿⣿⡇⡄⠀⣿⣿⢸⣿⡇⠈⢿⡇⠀⣿⠇⣷⣯⣃⣙⣛⣛⣵⣿⣿⣿⣿⣿⣿⣾⣝⡻⣿⣿⣿⣿⣿⣿⣿⣷⣮⣄⡈⠳⢜⠃⠘⢎⢿⣿⣿⣞⢿⣿⣿⣿⣿⣿⣷⣆⡀⠀⠘⣿⡟⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣟⣽⣿⣯⣿⣣⣿⣿⣿⣿⣿⡄⢿⣿⣿⣿⣿⣿⣿⡿⠟⣡⣾⣿⣿⣿⣿⣿⣿⡏⣰⣿⣿⡿⢹⡇⣾⠀⣿⣿⣾⣿⡇⠀⠈⢷⢰⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣽⣻⢿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣦⣀⡀⠈⠛⠻⢿⣎⢿⣿⣿⣿⣿⣿⣿⣷⣄⠀⣿⡇⠘⣧⠀⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⢟⣾⣿⣻⡿⣵⣿⣿⣿⣿⣟⣾⣯⠌⠛⠛⠛⠛⠉⠁⢠⣾⣿⣿⣿⣿⣿⣿⡿⠏⣰⣿⣿⡿⠁⢸⡇⢿⠀⣿⣿⣿⣿⡇⠀⠠⠈⠂⢃⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣻⡿⣿⣿⣿⣿⣿⣿⣿⡇⡄⠾⣲⣾⣿⣌⢿⣿⣿⣿⣿⣿⣿⣿⣧⡙⠇⠀⢻⡄⠀⠀⠀⠀⠀⠀⠀
|
||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⠚⠛⠛⠛⠚⠛⠛⠛⠛⠛⠚⠛⠛⠓⠚⠒⠛⠒⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠚⠛⠛⠛⠛⠓⠛⠛⠛⠓⠛⠛⠛⠛⠓⠚⠛⠛⠓⠚⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠚⠛⠛⠛⠛⠛⠛⠓⠛⠚⠛⠛⠛⠛⠓⠛⠛⠛⠛⠛⠛⠛⠛⠛⠒⠚⠚⠁⠀⠀⠀⠀⠀⠀⠀
|
||
"""
|
||
] = {"en": ":a1:", "status": 2, "E": 0.6, "alias": [":a1:"]}
|
||
|
||
|
||
ALLOWED_TAGS = list(bleach.sanitizer.ALLOWED_TAGS) + [
|
||
"img", "video", "audio", "source", "iframe", "picture", "span"
|
||
]
|
||
ALLOWED_ATTRIBUTES = {
|
||
**bleach.sanitizer.ALLOWED_ATTRIBUTES,
|
||
"img": ["src", "alt", "title", "width", "height"],
|
||
"a": ["href", "title", "target", "rel", "referrerpolicy", "class"],
|
||
"iframe": ["src", "width", "height", "frameborder", "allow", "allowfullscreen", "title", "referrerpolicy", "style"],
|
||
"video": ["src", "controls", "width", "height"],
|
||
"audio": ["src", "controls"],
|
||
"source": ["src", "type"],
|
||
"span": ["class"],
|
||
"picture": [],
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
def sanitize_html(value):
|
||
return bleach.clean(
|
||
value,
|
||
tags=ALLOWED_TAGS,
|
||
attributes=ALLOWED_ATTRIBUTES,
|
||
protocols=bleach.sanitizer.ALLOWED_PROTOCOLS + ["data"],
|
||
strip=True,
|
||
)
|
||
|
||
|
||
|
||
def set_link_target_blank(text):
|
||
soup = BeautifulSoup(text, "html.parser")
|
||
|
||
for element in soup.find_all("a"):
|
||
element.attrs["target"] = "_blank"
|
||
element.attrs["rel"] = "noopener noreferrer"
|
||
element.attrs["referrerpolicy"] = "no-referrer"
|
||
element.attrs["href"] = element.attrs["href"].strip(".").strip(",")
|
||
|
||
return str(soup)
|
||
|
||
SAFE_ATTRIBUTES = {
|
||
'href', 'src', 'alt', 'title', 'width', 'height', 'style', 'id', 'class',
|
||
'rel', 'type', 'name', 'value', 'placeholder', 'aria-hidden', 'aria-label', 'srcset'
|
||
}
|
||
|
||
def whitelist_attributes(html):
|
||
soup = BeautifulSoup(html, 'html.parser')
|
||
|
||
for tag in soup.find_all():
|
||
if hasattr(tag, 'attrs'):
|
||
if tag.name in ['script','form','input']:
|
||
tag.replace_with('')
|
||
continue
|
||
attrs = dict(tag.attrs)
|
||
for attr in list(attrs):
|
||
# Check if attribute is in the safe list or is a data-* attribute
|
||
if not (attr in SAFE_ATTRIBUTES or attr.startswith('data-')):
|
||
del tag.attrs[attr]
|
||
return str(soup)
|
||
|
||
|
||
def embed_youtube(text):
|
||
soup = BeautifulSoup(text, "html.parser")
|
||
for element in soup.find_all("a"):
|
||
# Check if the link is a YouTube link
|
||
url = urlparse(element["href"])
|
||
if (
|
||
url.hostname in ["www.youtu.be", "youtu.be"]
|
||
or url.hostname
|
||
in [
|
||
"www.youtube.com",
|
||
"youtube.com",
|
||
"www.youtube-nocookie.com",
|
||
"youtube-nocookie.com",
|
||
]
|
||
and any(url.path.startswith(p) for p in ["/watch", "/embed"])
|
||
):
|
||
queries = parse_qs(url.query)
|
||
if "v" in queries:
|
||
video_name = queries["v"][0]
|
||
else:
|
||
video_name = url.path.split("/")[-1]
|
||
|
||
queries.pop("v", None)
|
||
|
||
start_time = queries.get("t", None)
|
||
if start_time:
|
||
queries.pop("t", None)
|
||
queries["start"] = []
|
||
for t in start_time:
|
||
if t.endswith("s"):
|
||
t = t[:-1]
|
||
if t.isdigit():
|
||
queries["start"].append(t)
|
||
else:
|
||
queries["start"].append(
|
||
str(
|
||
sum(
|
||
int(x) * 60**i
|
||
for i, x in enumerate(reversed(t.split(":")))
|
||
)
|
||
)
|
||
)
|
||
|
||
new_queries = "&".join(
|
||
[f"{key}={v}" for key, value in queries.items() for v in value]
|
||
)
|
||
|
||
base_url = (
|
||
"youtube-nocookie.com"
|
||
if "youtube-nocookie" in url.hostname
|
||
else "youtube.com"
|
||
)
|
||
|
||
embed_template = f'<iframe width="560" height="315" style="display:block" src="https://www.{base_url}/embed/{video_name}?{new_queries}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>'
|
||
element.replace_with(BeautifulSoup(embed_template, "html.parser"))
|
||
|
||
return str(soup)
|
||
|
||
|
||
def embed_image(text):
|
||
soup = BeautifulSoup(text, "html.parser")
|
||
for element in soup.find_all("a"):
|
||
file_mime = mimetypes.guess_type(element.attrs["href"])[0]
|
||
|
||
if (
|
||
file_mime
|
||
and file_mime.startswith("image/")
|
||
or any(
|
||
ext in element.attrs["href"].lower()
|
||
for ext in [
|
||
".png",
|
||
".jpg",
|
||
".jpeg",
|
||
".gif",
|
||
".webp",
|
||
".svg",
|
||
".bmp",
|
||
".tiff",
|
||
".ico",
|
||
".heif",
|
||
".heic",
|
||
]
|
||
)
|
||
):
|
||
embed_template = f'<img src="{element.attrs["href"]}" title="{element.attrs["href"]}?width=420" alt="{element.attrs["href"]}" />'
|
||
element.replace_with(BeautifulSoup(embed_template, "html.parser"))
|
||
return str(soup)
|
||
|
||
|
||
def enrich_image_rendering(text):
|
||
soup = BeautifulSoup(text, "html.parser")
|
||
for element in soup.find_all("img"):
|
||
if element.attrs["src"].startswith("/"):
|
||
element.attrs["src"] += "?width=240&height=240"
|
||
picture_template = f'''
|
||
<picture>
|
||
<source srcset="{element.attrs["src"]}" type="{mimetypes.guess_type(element.attrs["src"])[0]}" />
|
||
<source srcset="{element.attrs["src"]}&format=webp" type="image/webp" />
|
||
<img src="{element.attrs["src"]}&format=png" title="{element.attrs["src"]}" alt="{element.attrs["src"]}" />
|
||
</picture>'''
|
||
element.replace_with(BeautifulSoup(picture_template, "html.parser"))
|
||
return str(soup)
|
||
|
||
|
||
def embed_media(text):
|
||
soup = BeautifulSoup(text, "html.parser")
|
||
for element in soup.find_all("a"):
|
||
for extension in [
|
||
".mp4",
|
||
".mp3",
|
||
".wav",
|
||
".ogg",
|
||
".webm",
|
||
".flac",
|
||
".aac",
|
||
".mpg",
|
||
".avi",
|
||
".wmv",
|
||
]:
|
||
if extension in element.attrs["href"].lower():
|
||
embed_template = f'<video controls> <source src="{element.attrs["href"]}">Your browser does not support the video tag.</video>'
|
||
element.replace_with(BeautifulSoup(embed_template, "html.parser"))
|
||
return str(soup)
|
||
|
||
|
||
def linkify_https(text):
|
||
if "https://" not in text:
|
||
return text
|
||
url_pattern = r'(?<!["\'])\bhttps://[^\s<>()]+(?<!\.)'
|
||
|
||
soup = BeautifulSoup(text, "html.parser")
|
||
|
||
for element in soup.find_all(text=True):
|
||
parent = element.parent
|
||
if parent.name in ["a", "script", "style"]:
|
||
continue
|
||
|
||
new_text = re.sub(url_pattern, r'<a href="\g<0>">\g<0></a>', element)
|
||
element.replace_with(BeautifulSoup(new_text, "html.parser"))
|
||
|
||
return set_link_target_blank(str(soup))
|
||
|
||
|
||
@time_cache(timeout=60*60)
|
||
def get_url_content(url):
|
||
try:
|
||
response = requests.get(url, timeout=5)
|
||
response.raise_for_status()
|
||
return response.text
|
||
except Exception as e:
|
||
print(f"Error fetching {url}: {e}")
|
||
return None
|
||
|
||
|
||
def embed_url(text):
|
||
soup = BeautifulSoup(text, "html.parser")
|
||
|
||
attachments = {}
|
||
|
||
for element in soup.find_all("a"):
|
||
if "href" in element.attrs and element.attrs["href"].startswith("http"):
|
||
page_url = urlparse(element.attrs["href"])
|
||
page = get_url_content(element.attrs["href"])
|
||
if page:
|
||
parsed_page = BeautifulSoup(page, "html.parser")
|
||
head_info = parsed_page.find("head")
|
||
if head_info:
|
||
|
||
def get_element_options(
|
||
elem=None, meta=None, ograph=None, twitter=None
|
||
):
|
||
if twitter:
|
||
tw_tag = head_info.find(
|
||
"meta", attrs={"name": "twitter:" + twitter}
|
||
) or head_info.find(
|
||
"meta", attrs={"property": "twitter:" + twitter}
|
||
)
|
||
if tw_tag:
|
||
return tw_tag.get("content", tw_tag.get("value", None))
|
||
|
||
if ograph:
|
||
og_tag = head_info.find(
|
||
"meta", attrs={"property": "og:" + ograph}
|
||
) or head_info.find("meta", attrs={"name": "og:" + ograph})
|
||
if og_tag:
|
||
return og_tag.get("content", og_tag.get("value", None))
|
||
|
||
if meta:
|
||
meta_tag = head_info.find(
|
||
"meta", attrs={"name": meta}
|
||
) or head_info.find("meta", attrs={"property": meta})
|
||
if meta_tag:
|
||
return meta_tag.get(
|
||
"content", meta_tag.get("value", None)
|
||
)
|
||
|
||
if elem:
|
||
elem_tag = head_info.find(elem)
|
||
if elem_tag:
|
||
return elem_tag.text
|
||
|
||
return None
|
||
|
||
original_link_name = element.attrs["href"]
|
||
|
||
if original_link_name in attachments:
|
||
continue
|
||
|
||
page_name = (
|
||
get_element_options("title", "title", "title", "title")
|
||
or page_url.netloc
|
||
)
|
||
page_site = (
|
||
get_element_options(None, "site", "site", "site")
|
||
or page_url.netloc
|
||
)
|
||
page_description = get_element_options(
|
||
None, "description", "description", "description"
|
||
)
|
||
page_image = get_element_options(None, "image", "image", "image")
|
||
page_image_alt = get_element_options(
|
||
None, "image:alt", "image:alt", "image:alt"
|
||
)
|
||
page_video = get_element_options(None, "video", "video", "video")
|
||
page_audio = get_element_options(None, "audio", "audio", "audio")
|
||
|
||
preview_size = (
|
||
get_element_options(None, None, None, "card")
|
||
or "summary_large_image"
|
||
)
|
||
|
||
|
||
attachment_base = BeautifulSoup(str(element), "html.parser")
|
||
attachments[original_link_name] = attachment_base
|
||
|
||
attachment = next(attachment_base.children)
|
||
|
||
attachment.clear()
|
||
attachment.attrs["class"] = "embed-url-link"
|
||
|
||
render_element = attachment
|
||
|
||
if page_image:
|
||
image_template = f'<span><img src="{page_image}" alt="{page_image_alt or page_name}" title="{page_name}" width="420" height="240" /></span>'
|
||
render_element.append(
|
||
BeautifulSoup(image_template, "html.parser")
|
||
)
|
||
if page_video:
|
||
video_template = f'<video controls><source src="{page_video}">Your browser does not support the video tag.</video>'
|
||
render_element.append(
|
||
BeautifulSoup(video_template, "html.parser")
|
||
)
|
||
if page_audio:
|
||
audio_template = f'<audio controls><source src="{page_audio}">Your browser does not support the audio tag.</audio>'
|
||
render_element.append(
|
||
BeautifulSoup(audio_template, "html.parser")
|
||
)
|
||
|
||
description_element_base = BeautifulSoup(
|
||
"<span class='description'></span>", "html.parser"
|
||
)
|
||
description_element = next(description_element_base.children)
|
||
description_element.append(
|
||
BeautifulSoup(
|
||
f'<p class="page-site">{page_site}</p>',
|
||
"html.parser",
|
||
)
|
||
)
|
||
|
||
description_element.append(
|
||
BeautifulSoup(f'<strong class="page-name">{page_name}</strong>', "html.parser")
|
||
)
|
||
|
||
description_element.append(
|
||
BeautifulSoup(f"<p class='page-description'>{page_description or "No description available."}</p>", "html.parser")
|
||
)
|
||
|
||
description_element.append(
|
||
BeautifulSoup(f"<p class='page-original-link'>{original_link_name}</p>", "html.parser")
|
||
)
|
||
|
||
render_element.append(description_element_base)
|
||
|
||
|
||
for attachment in attachments.values():
|
||
soup.append(attachment)
|
||
|
||
return str(soup)
|
||
|
||
|
||
class EmojiExtension(Extension):
|
||
tags = {"emoji"}
|
||
|
||
def parse(self, parser):
|
||
line_number = next(parser.stream).lineno
|
||
md_file = [Const("")]
|
||
body = ""
|
||
try:
|
||
md_file = [parser.parse_expression()]
|
||
except TemplateSyntaxError:
|
||
body = parser.parse_statements(["name:endemoji"], drop_needle=True)
|
||
return nodes.CallBlock(
|
||
self.call_method("_to_html", md_file), [], [], body
|
||
).set_lineno(line_number)
|
||
|
||
def _to_html(self, md_file, caller):
|
||
return emoji.emojize(caller(), language="alias")
|
||
|
||
|
||
class LinkifyExtension(Extension):
|
||
tags = {"linkify"}
|
||
|
||
def __init__(self, environment):
|
||
self.app = SimpleNamespace(jinja2_env=environment)
|
||
super(LinkifyExtension, self).__init__(environment)
|
||
|
||
def parse(self, parser):
|
||
line_number = next(parser.stream).lineno
|
||
md_file = [Const("")]
|
||
body = ""
|
||
try:
|
||
md_file = [parser.parse_expression()]
|
||
except TemplateSyntaxError:
|
||
body = parser.parse_statements(["name:endlinkify"], drop_needle=True)
|
||
return nodes.CallBlock(
|
||
self.call_method("_to_html", md_file), [], [], body
|
||
).set_lineno(line_number)
|
||
|
||
def _to_html(self, md_file, caller):
|
||
result = linkify_https(caller())
|
||
result = embed_media(result)
|
||
result = embed_image(result)
|
||
result = embed_youtube(result)
|
||
|
||
result = enrich_image_rendering(result)
|
||
|
||
result = embed_url(result)
|
||
|
||
return result
|
||
|
||
|
||
class PythonExtension(Extension):
|
||
tags = {"py3"}
|
||
|
||
def parse(self, parser):
|
||
line_number = next(parser.stream).lineno
|
||
md_file = [Const("")]
|
||
body = ""
|
||
try:
|
||
md_file = [parser.parse_expression()]
|
||
except TemplateSyntaxError:
|
||
body = parser.parse_statements(["name:endpy3"], drop_needle=True)
|
||
return nodes.CallBlock(
|
||
self.call_method("_to_html", md_file), [], [], body
|
||
).set_lineno(line_number)
|
||
|
||
def _to_html(self, md_file, caller):
|
||
def fn(source):
|
||
import subprocess
|
||
|
||
def system(command):
|
||
if isinstance(command):
|
||
command = command.split(" ")
|
||
from io import StringIO
|
||
|
||
stdout = StringIO()
|
||
subprocess.run(command, stderr=stdout, stdout=stdout, text=True)
|
||
return stdout.getvalue()
|
||
|
||
to_write = []
|
||
|
||
def render(text):
|
||
global to_write
|
||
to_write.append(text)
|
||
|
||
exec(source)
|
||
return "".join(to_write)
|
||
|
||
return str(fn(caller()))
|