Compare commits
7 Commits
414bfefb21
...
v.1.2
Author | SHA1 | Date | |
---|---|---|---|
95be1e72d3 | |||
234b73add0 | |||
f4fb85f7a4 | |||
4fba305b05 | |||
68c7cc0ada | |||
79304816b0 | |||
2cfa5a8f8d |
@@ -51,8 +51,7 @@
|
|||||||
"enabled": false
|
"enabled": false
|
||||||
},
|
},
|
||||||
"spoilers": {
|
"spoilers": {
|
||||||
"enabled": true,
|
"enabled": true
|
||||||
"allow_external": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scheduler": {
|
"scheduler": {
|
||||||
@@ -75,11 +74,6 @@
|
|||||||
"cache_admins": {
|
"cache_admins": {
|
||||||
"interval": 120,
|
"interval": 120,
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
|
||||||
"channels_monitor": {
|
|
||||||
"interval": 5,
|
|
||||||
"enabled": true,
|
|
||||||
"channels": []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"locations": {
|
"locations": {
|
||||||
|
@@ -31,7 +31,7 @@ from modules.commands.warnings import *
|
|||||||
from modules.callbacks.nothing import *
|
from modules.callbacks.nothing import *
|
||||||
from modules.callbacks.reapply import *
|
from modules.callbacks.reapply import *
|
||||||
from modules.callbacks.rules import *
|
from modules.callbacks.rules import *
|
||||||
from modules.callbacks.spoiler import *
|
from modules.callbacks.sid import *
|
||||||
from modules.callbacks.sponsorship import *
|
from modules.callbacks.sponsorship import *
|
||||||
from modules.callbacks.sub import *
|
from modules.callbacks.sub import *
|
||||||
from modules.callbacks.sus import *
|
from modules.callbacks.sus import *
|
||||||
|
@@ -109,9 +109,7 @@
|
|||||||
"spoiler_unfinished": "У вас ще є незавершений спойлер. Надішліть /cancel щоб зупинити його створення",
|
"spoiler_unfinished": "У вас ще є незавершений спойлер. Надішліть /cancel щоб зупинити його створення",
|
||||||
"spoiler_cancel": "Створення спойлера було припинено",
|
"spoiler_cancel": "Створення спойлера було припинено",
|
||||||
"spoiler_empty": "Спойлер категорії \"{0}\" без опису",
|
"spoiler_empty": "Спойлер категорії \"{0}\" без опису",
|
||||||
"spoiler_empty_named": "Спойлер категорії \"{0}\" без опису від **{1}**",
|
|
||||||
"spoiler_described": "Спойлер категорії \"{0}\": {1}",
|
"spoiler_described": "Спойлер категорії \"{0}\": {1}",
|
||||||
"spoiler_described_named": "Спойлер категорії \"{0}\" від **{1}**: {2}",
|
|
||||||
"spoiler_description_enter": "Добре, введіть бажаний опис спойлера",
|
"spoiler_description_enter": "Добре, введіть бажаний опис спойлера",
|
||||||
"spoiler_description_too_long": "Текст занадто довгий. Будь ласка, умісти опис у 1024 символи.",
|
"spoiler_description_too_long": "Текст занадто довгий. Будь ласка, умісти опис у 1024 символи.",
|
||||||
"spoiler_using_description": "Встановлено опис спойлера: {0}\n\nЗалишилось додати вміст самого спойлера. Бот приймає текстове повідомлення, фото, відео, файл а також гіф зображення (1 шт.)",
|
"spoiler_using_description": "Встановлено опис спойлера: {0}\n\nЗалишилось додати вміст самого спойлера. Бот приймає текстове повідомлення, фото, відео, файл а також гіф зображення (1 шт.)",
|
||||||
@@ -120,7 +118,6 @@
|
|||||||
"spoiler_incorrect_content": "Бот не підтримує такий контент. Будь ласка, надішли текст, фото, відео, файл або анімацію (гіф).",
|
"spoiler_incorrect_content": "Бот не підтримує такий контент. Будь ласка, надішли текст, фото, відео, файл або анімацію (гіф).",
|
||||||
"spoiler_incorrect_category": "Вказана категорія не є дійсною. Будь ласка, користуйся клавіатурою бота (кнопка біля 📎) для вибору категорії.",
|
"spoiler_incorrect_category": "Вказана категорія не є дійсною. Будь ласка, користуйся клавіатурою бота (кнопка біля 📎) для вибору категорії.",
|
||||||
"spoiler_in_progress": "❌ **Дія неможлива**\nПерш ніж починати нову дію, треба завершити створення спойлера або перервати його командою /cancel.",
|
"spoiler_in_progress": "❌ **Дія неможлива**\nПерш ніж починати нову дію, треба завершити створення спойлера або перервати його командою /cancel.",
|
||||||
"youtube_video": "На каналі [{0}]({1}) нове відео!\n\n**[{2}]({3})**",
|
|
||||||
"yes": "Так",
|
"yes": "Так",
|
||||||
"no": "Ні",
|
"no": "Ні",
|
||||||
"voice_message": [
|
"voice_message": [
|
||||||
@@ -229,10 +226,8 @@
|
|||||||
"done": "✅ Готово",
|
"done": "✅ Готово",
|
||||||
"sponsor_apply": "Заповнити форму",
|
"sponsor_apply": "Заповнити форму",
|
||||||
"sponsor_started": "Форму розпочато",
|
"sponsor_started": "Форму розпочато",
|
||||||
"spoiler_view": "Переглянути",
|
"spoiler_send": "Надіслати",
|
||||||
"spoiler_preview": "Попередній перегляд",
|
"spoiler_view": "Переглянути"
|
||||||
"spoiler_send_chat": "Надіслати в холо-чат",
|
|
||||||
"spoiler_send_other": "Надіслати в інший чат"
|
|
||||||
},
|
},
|
||||||
"callback": {
|
"callback": {
|
||||||
"sub_accepted": "✅ Анкету {0} схвалено",
|
"sub_accepted": "✅ Анкету {0} схвалено",
|
||||||
@@ -248,8 +243,7 @@
|
|||||||
"reapply_stopped": "ℹ️ Перервано заповнення анкети",
|
"reapply_stopped": "ℹ️ Перервано заповнення анкети",
|
||||||
"sponsor_started": "ℹ️ Заповнення форми розпочато",
|
"sponsor_started": "ℹ️ Заповнення форми розпочато",
|
||||||
"sponsor_accepted": "✅ Форму {0} схвалено",
|
"sponsor_accepted": "✅ Форму {0} схвалено",
|
||||||
"sponsor_rejected": "❌ Форму {0} відхилено",
|
"sponsor_rejected": "❌ Форму {0} відхилено"
|
||||||
"spoiler_sent": "✅ Повідомлення надіслано в холо-чат"
|
|
||||||
},
|
},
|
||||||
"inline": {
|
"inline": {
|
||||||
"forbidden": {
|
"forbidden": {
|
||||||
|
10
modules/callbacks/sid.py
Normal file
10
modules/callbacks/sid.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from app import app
|
||||||
|
from pyrogram.types import CallbackQuery
|
||||||
|
from pyrogram.client import Client
|
||||||
|
from pyrogram import filters
|
||||||
|
|
||||||
|
# Callback rule ================================================================================================================
|
||||||
|
@app.on_callback_query(filters.regex("sid_[\s\S]*"))
|
||||||
|
async def callback_query_rule(app: Client, clb: CallbackQuery):
|
||||||
|
await clb.answer(url=f'https://t.me/{(await app.get_me()).username}?start={clb.data.split("_")[1]}')
|
||||||
|
# ==============================================================================================================================
|
@@ -1,31 +0,0 @@
|
|||||||
from app import app
|
|
||||||
from pyrogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
|
||||||
from pyrogram.client import Client
|
|
||||||
from pyrogram import filters
|
|
||||||
from modules.database import col_spoilers
|
|
||||||
from bson.objectid import ObjectId
|
|
||||||
|
|
||||||
from modules.utils import configGet, locale
|
|
||||||
|
|
||||||
# Callback sid =================================================================================================================
|
|
||||||
@app.on_callback_query(filters.regex("sid_[\s\S]*"))
|
|
||||||
async def callback_query_sid(app: Client, clb: CallbackQuery):
|
|
||||||
await clb.answer(url=f'https://t.me/{(await app.get_me()).username}?start={clb.data.split("_")[1]}')
|
|
||||||
# ==============================================================================================================================
|
|
||||||
|
|
||||||
# Callback shc =================================================================================================================
|
|
||||||
@app.on_callback_query(filters.regex("shc_[\s\S]*"))
|
|
||||||
async def callback_query_shc(app: Client, clb: CallbackQuery):
|
|
||||||
|
|
||||||
spoil = col_spoilers.find_one( {"_id": ObjectId(clb.data.split("_")[1])} )
|
|
||||||
|
|
||||||
if spoil["description"] == "":
|
|
||||||
desc = locale("spoiler_empty_named", "message", locale=clb.from_user).format(locale(spoil["category"], "message", "spoiler_categories"), clb.from_user.first_name)
|
|
||||||
else:
|
|
||||||
desc = locale("spoiler_described_named", "message", locale=clb.from_user).format(locale(spoil["category"], "message", "spoiler_categories"), clb.from_user.first_name, spoil["description"])
|
|
||||||
|
|
||||||
await app.send_message(configGet("users", "groups"), desc, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_view", "button", locale=clb.from_user), callback_data=f'sid_{clb.data.split("_")[1]}')]]))
|
|
||||||
await app.send_message(configGet("admin", "groups"), desc, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_view", "button", locale=clb.from_user), callback_data=f'sid_{clb.data.split("_")[1]}')]]))
|
|
||||||
|
|
||||||
await clb.answer(locale("spoiler_sent", "callback", locale=clb.from_user), show_alert=True)
|
|
||||||
# ==============================================================================================================================
|
|
@@ -51,7 +51,7 @@ async def cmd_identify(app: Client, msg: Message):
|
|||||||
if user.photo is not None:
|
if user.photo is not None:
|
||||||
await app.send_chat_action(msg.chat.id, action=ChatAction.UPLOAD_PHOTO)
|
await app.send_chat_action(msg.chat.id, action=ChatAction.UPLOAD_PHOTO)
|
||||||
await msg.reply_photo(
|
await msg.reply_photo(
|
||||||
create_tmp((await download_tmp(app, user.photo.big_file_id))[1], kind="image"),
|
create_tmp(await download_tmp(app, user.photo.big_file_id), kind="image"),
|
||||||
quote=should_quote(msg),
|
quote=should_quote(msg),
|
||||||
caption=output
|
caption=output
|
||||||
)
|
)
|
||||||
|
@@ -10,7 +10,7 @@ from modules.database import col_spoilers
|
|||||||
from modules import custom_filters
|
from modules import custom_filters
|
||||||
|
|
||||||
# Spoiler command ==============================================================================================================
|
# Spoiler command ==============================================================================================================
|
||||||
@app.on_message(custom_filters.enabled_spoilers & custom_filters.member & ~filters.scheduled & filters.private & filters.command(["spoiler"], prefixes=["/"]))
|
@app.on_message(custom_filters.member & ~filters.scheduled & filters.private & filters.command(["spoiler"], prefixes=["/"]))
|
||||||
async def cmd_spoiler(app: Client, msg: Message):
|
async def cmd_spoiler(app: Client, msg: Message):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@@ -39,9 +39,6 @@ async def enabled_invites_check_func(_, __, msg: Message):
|
|||||||
async def enabled_dinovoice_func(_, __, msg: Message):
|
async def enabled_dinovoice_func(_, __, msg: Message):
|
||||||
return configGet("enabled", "features", "dinovoice")
|
return configGet("enabled", "features", "dinovoice")
|
||||||
|
|
||||||
async def enabled_spoilers_func(_, __, msg: Message):
|
|
||||||
return configGet("enabled", "features", "spoilers")
|
|
||||||
|
|
||||||
async def filling_sponsorship_func(_, __, msg: Message):
|
async def filling_sponsorship_func(_, __, msg: Message):
|
||||||
return True if col_tmp.find_one({"user": msg.from_user.id, "type": "sponsorship"}) is not None else False
|
return True if col_tmp.find_one({"user": msg.from_user.id, "type": "sponsorship"}) is not None else False
|
||||||
|
|
||||||
@@ -55,6 +52,5 @@ enabled_sponsorships = filters.create(enabled_sponsorships_func)
|
|||||||
enabled_warnings = filters.create(enabled_warnings_func)
|
enabled_warnings = filters.create(enabled_warnings_func)
|
||||||
enabled_invites_check = filters.create(enabled_invites_check_func)
|
enabled_invites_check = filters.create(enabled_invites_check_func)
|
||||||
enabled_dinovoice = filters.create(enabled_dinovoice_func)
|
enabled_dinovoice = filters.create(enabled_dinovoice_func)
|
||||||
enabled_spoilers = filters.create(enabled_spoilers_func)
|
|
||||||
|
|
||||||
filling_sponsorship = filters.create(filling_sponsorship_func)
|
filling_sponsorship = filters.create(filling_sponsorship_func)
|
@@ -28,14 +28,13 @@ db = db_client.get_database(name=db_config["name"])
|
|||||||
|
|
||||||
collections = db.list_collection_names()
|
collections = db.list_collection_names()
|
||||||
|
|
||||||
for collection in ["tmp", "users", "context", "youtube", "spoilers", "messages", "warnings", "applications", "sponsorships"]:
|
for collection in ["tmp", "users", "context", "spoilers", "messages", "warnings", "applications", "sponsorships"]:
|
||||||
if not collection in collections:
|
if not collection in collections:
|
||||||
db.create_collection(collection)
|
db.create_collection(collection)
|
||||||
|
|
||||||
col_tmp = db.get_collection("tmp")
|
col_tmp = db.get_collection("tmp")
|
||||||
col_users = db.get_collection("users")
|
col_users = db.get_collection("users")
|
||||||
col_context = db.get_collection("context")
|
col_context = db.get_collection("context")
|
||||||
col_youtube = db.get_collection("youtube")
|
|
||||||
col_spoilers = db.get_collection("spoilers")
|
col_spoilers = db.get_collection("spoilers")
|
||||||
col_messages = db.get_collection("messages")
|
col_messages = db.get_collection("messages")
|
||||||
col_warnings = db.get_collection("warnings")
|
col_warnings = db.get_collection("warnings")
|
||||||
|
@@ -66,9 +66,6 @@ async def any_stage(app: Client, msg: Message):
|
|||||||
|
|
||||||
if holo_user.application_state()[0] != "fill" and holo_user.sponsorship_state()[0] != "fill":
|
if holo_user.application_state()[0] != "fill" and holo_user.sponsorship_state()[0] != "fill":
|
||||||
|
|
||||||
if configGet("enabled", "features", "spoilers") is False:
|
|
||||||
return
|
|
||||||
|
|
||||||
spoiler = col_spoilers.find_one( {"user": msg.from_user.id, "completed": False} )
|
spoiler = col_spoilers.find_one( {"user": msg.from_user.id, "completed": False} )
|
||||||
|
|
||||||
if spoiler is None:
|
if spoiler is None:
|
||||||
@@ -149,37 +146,7 @@ async def any_stage(app: Client, msg: Message):
|
|||||||
ready = True
|
ready = True
|
||||||
|
|
||||||
if ready is True:
|
if ready is True:
|
||||||
if configGet("allow_external", "features", "spoilers") is True:
|
await msg.reply_text(locale("spoiler_ready", "message", locale=msg.from_user), reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(locale("spoiler_send", "button", locale=msg.from_user), switch_inline_query=f"spoiler:{spoiler['_id'].__str__()}")]]))
|
||||||
await msg.reply_text(
|
|
||||||
locale("spoiler_ready", "message", locale=msg.from_user),
|
|
||||||
reply_markup=InlineKeyboardMarkup(
|
|
||||||
[
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(locale("spoiler_preview", "button", locale=msg.from_user), callback_data=f"sid_{spoiler['_id'].__str__()}")
|
|
||||||
],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(locale("spoiler_send_chat", "button", locale=msg.from_user), callback_data=f"shc_{spoiler['_id'].__str__()}")
|
|
||||||
],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(locale("spoiler_send_other", "button", locale=msg.from_user), switch_inline_query=f"spoiler:{spoiler['_id'].__str__()}")
|
|
||||||
]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await msg.reply_text(
|
|
||||||
locale("spoiler_ready", "message", locale=msg.from_user),
|
|
||||||
reply_markup=InlineKeyboardMarkup(
|
|
||||||
[
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(locale("spoiler_preview", "button", locale=msg.from_user), callback_data=f"sid_{spoiler['_id'].__str__()}")
|
|
||||||
],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(locale("spoiler_send_chat", "button", locale=msg.from_user), callback_data=f"shc_{spoiler['_id'].__str__()}")
|
|
||||||
]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await msg.reply_text(locale("spoiler_incorrect_content", "message", locale=msg.from_user))
|
await msg.reply_text(locale("spoiler_incorrect_content", "message", locale=msg.from_user))
|
||||||
|
|
||||||
|
@@ -22,8 +22,6 @@ async def inline_answer(client: Client, inline_query: InlineQuery):
|
|||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
if configGet("allow_external", "features", "spoilers") is True:
|
|
||||||
|
|
||||||
if inline_query.query.startswith("spoiler:"):
|
if inline_query.query.startswith("spoiler:"):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
"""Automatically register commands and execute
|
"""Automatically register commands and execute
|
||||||
some scheduled tasks is the main idea of this module"""
|
some scheduled tasks is the main idea of this module"""
|
||||||
|
|
||||||
from asyncio import sleep
|
|
||||||
from os import listdir, makedirs, path, sep
|
from os import listdir, makedirs, path, sep
|
||||||
from traceback import format_exc
|
|
||||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from ujson import dumps
|
from ujson import dumps
|
||||||
@@ -14,9 +12,7 @@ from pyrogram.enums.chat_members_filter import ChatMembersFilter
|
|||||||
from classes.holo_user import HoloUser
|
from classes.holo_user import HoloUser
|
||||||
from modules.utils import configGet, jsonSave, locale, logWrite
|
from modules.utils import configGet, jsonSave, locale, logWrite
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from modules.database import col_applications, col_sponsorships, col_youtube
|
from modules.database import col_applications, col_sponsorships
|
||||||
from xmltodict import parse
|
|
||||||
from requests import get
|
|
||||||
|
|
||||||
scheduler = AsyncIOScheduler()
|
scheduler = AsyncIOScheduler()
|
||||||
|
|
||||||
@@ -238,29 +234,3 @@ async def commands_register():
|
|||||||
if configGet("debug") is True:
|
if configGet("debug") is True:
|
||||||
print(commands, flush=True)
|
print(commands, flush=True)
|
||||||
logWrite(f"Complete commands registration:\n{dumps(commands_raw, indent=4, ensure_ascii=False, encode_html_chars=False)}", debug=True)
|
logWrite(f"Complete commands registration:\n{dumps(commands_raw, indent=4, ensure_ascii=False, encode_html_chars=False)}", debug=True)
|
||||||
|
|
||||||
|
|
||||||
if configGet("enabled", "scheduler", "channels_monitor"):
|
|
||||||
@scheduler.scheduled_job(trigger="interval", minutes=configGet("interval", "scheduler", "channels_monitor"))
|
|
||||||
async def channels_monitor():
|
|
||||||
for channel in configGet("channels", "scheduler", "channels_monitor"):
|
|
||||||
if configGet("debug") is True:
|
|
||||||
logWrite(f'Processing videos of {channel["name"]} ({channel["id"]})', debug=True)
|
|
||||||
try:
|
|
||||||
req = get(f'https://www.youtube.com/feeds/videos.xml?channel_id={channel["id"]}')
|
|
||||||
parsed = parse(req.content)
|
|
||||||
if "feed" not in parsed:
|
|
||||||
continue
|
|
||||||
if "entry" not in parsed["feed"]:
|
|
||||||
continue
|
|
||||||
for entry in parsed["feed"]["entry"]:
|
|
||||||
if "yt:videoId" not in entry:
|
|
||||||
continue
|
|
||||||
if col_youtube.find_one( {"channel": channel["id"], "video": entry["yt:videoId"]} ) is None:
|
|
||||||
col_youtube.insert_one( {"channel": channel["id"], "video": entry["yt:videoId"], "date": datetime.fromisoformat(entry["published"])} )
|
|
||||||
await app.send_message(configGet("users", "groups"), locale("youtube_video", "message").format(channel["name"], channel["link"], entry["title"], entry["link"]["@href"]), disable_web_page_preview=False)
|
|
||||||
await sleep(2)
|
|
||||||
except Exception as exp:
|
|
||||||
logWrite(f'Could not get last videos of {channel["name"]} ({channel["id"]}) due to {exp}: {format_exc()}')
|
|
||||||
if configGet("debug") is True:
|
|
||||||
logWrite("Admin group caching performed", debug=True)
|
|
@@ -1,4 +1,4 @@
|
|||||||
from typing import Any, Literal, Tuple, Union
|
from typing import Any, Literal, Union
|
||||||
from uuid import uuid1
|
from uuid import uuid1
|
||||||
from requests import get
|
from requests import get
|
||||||
from pyrogram.enums.chat_type import ChatType
|
from pyrogram.enums.chat_type import ChatType
|
||||||
@@ -212,7 +212,7 @@ def create_tmp(bytedata: Union[bytes, bytearray], kind: Union[Literal["image", "
|
|||||||
file.write(bytedata)
|
file.write(bytedata)
|
||||||
return path.join("tmp", filename)
|
return path.join("tmp", filename)
|
||||||
|
|
||||||
async def download_tmp(app: Client, file_id: str) -> Tuple[str, bytes]:
|
async def download_tmp(app: Client, file_id: str) -> bytes:
|
||||||
"""Download file by its ID and return its bytes
|
"""Download file by its ID and return its bytes
|
||||||
|
|
||||||
### Args:
|
### Args:
|
||||||
@@ -220,14 +220,14 @@ async def download_tmp(app: Client, file_id: str) -> Tuple[str, bytes]:
|
|||||||
* file_id (`str`): File's unique id
|
* file_id (`str`): File's unique id
|
||||||
|
|
||||||
### Returns:
|
### Returns:
|
||||||
* `Tuple[str, bytes]`: First is a filepath and the second is file's bytes
|
* `bytes`: Bytes of downloaded file
|
||||||
"""
|
"""
|
||||||
filename = str(uuid1())
|
filename = str(uuid1())
|
||||||
makedirs("tmp", exist_ok=True)
|
makedirs("tmp", exist_ok=True)
|
||||||
await app.download_media(file_id, path.join("tmp", filename))
|
await app.download_media(file_id, path.join("tmp", filename))
|
||||||
with open(path.join("tmp", filename), "rb") as f:
|
with open(path.join("tmp", filename), "rb") as f:
|
||||||
bytedata = f.read()
|
bytedata = f.read()
|
||||||
return path.join("tmp", filename), bytedata
|
return bytedata
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from psutil import Process
|
from psutil import Process
|
||||||
|
@@ -9,4 +9,3 @@ python_dateutil==2.8.2
|
|||||||
starlette~=0.22.0
|
starlette~=0.22.0
|
||||||
ujson~=5.7.0
|
ujson~=5.7.0
|
||||||
ftfy~=6.1.1
|
ftfy~=6.1.1
|
||||||
xmltodict~=0.13.0
|
|
Reference in New Issue
Block a user