Предварительная версия системы фильтрации ссылок
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
aiohappyeyeballs==2.6.1
|
aiohappyeyeballs==2.6.1
|
||||||
aiohttp==3.12.12
|
aiohttp==3.12.12
|
||||||
aiosignal==1.3.2
|
aiosignal==1.3.2
|
||||||
|
aiosqlite==0.21.0
|
||||||
async-timeout==5.0.1
|
async-timeout==5.0.1
|
||||||
asyncpg==0.30.0
|
asyncpg==0.30.0
|
||||||
attrs==25.3.0
|
attrs==25.3.0
|
||||||
certifi==2025.4.26
|
certifi==2025.4.26
|
||||||
charset-normalizer==3.4.2
|
charset-normalizer==3.4.2
|
||||||
disnake==2.10.1
|
disnake==2.10.1
|
||||||
|
filelock==3.18.0
|
||||||
frozenlist==1.7.0
|
frozenlist==1.7.0
|
||||||
greenlet==3.2.3
|
greenlet==3.2.3
|
||||||
idna==3.10
|
idna==3.10
|
||||||
@@ -14,7 +16,9 @@ multidict==6.4.4
|
|||||||
numpy==2.2.6
|
numpy==2.2.6
|
||||||
propcache==0.3.2
|
propcache==0.3.2
|
||||||
requests==2.32.4
|
requests==2.32.4
|
||||||
|
requests-file==2.1.0
|
||||||
SQLAlchemy==2.0.41
|
SQLAlchemy==2.0.41
|
||||||
|
tldextract==5.3.0
|
||||||
typing_extensions==4.14.0
|
typing_extensions==4.14.0
|
||||||
urllib3==2.4.0
|
urllib3==2.4.0
|
||||||
yarl==1.20.1
|
yarl==1.20.1
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from constants.global_constants import *
|
|||||||
from data.secrets.TOKENS import TOKENS
|
from data.secrets.TOKENS import TOKENS
|
||||||
from database.db_classes import all_data as DataBaseClasses
|
from database.db_classes import all_data as DataBaseClasses
|
||||||
from managers.DataBaseManager import DatabaseManager
|
from managers.DataBaseManager import DatabaseManager
|
||||||
from managers.old_DataBaseManager import DatabaseManager as old_DatabaseManager
|
|
||||||
from database.settings import config
|
from database.settings import config
|
||||||
|
|
||||||
from sqlalchemy.orm import declarative_base, relationship
|
from sqlalchemy.orm import declarative_base, relationship
|
||||||
@@ -25,6 +24,8 @@ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sess
|
|||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from sqlalchemy.schema import CreateTable
|
from sqlalchemy.schema import CreateTable
|
||||||
|
|
||||||
|
import tldextract
|
||||||
|
|
||||||
class AnyBots(commands.Bot):
|
class AnyBots(commands.Bot):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -291,22 +292,25 @@ class MainBot(AnyBots):
|
|||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
def __init__(self, DataBase, stop_event):
|
def __init__(self, DataBase, stop_event, task_start = True):
|
||||||
super().__init__(DataBase)
|
super().__init__(DataBase)
|
||||||
self.stop_event = stop_event
|
self.stop_event = stop_event
|
||||||
|
self.task_start = task_start
|
||||||
|
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
await super().on_ready()
|
await super().on_ready()
|
||||||
|
|
||||||
self.CheckDataBases.cancel()
|
if self.task_start:
|
||||||
self.MakeBackups.cancel()
|
self.CheckDataBases.cancel()
|
||||||
|
self.MakeBackups.cancel()
|
||||||
|
|
||||||
self.MakeBackups.start()
|
self.MakeBackups.start()
|
||||||
self.CheckDataBases.start()
|
self.CheckDataBases.start()
|
||||||
|
|
||||||
async def BotOff(self):
|
async def BotOff(self):
|
||||||
self.CheckDataBases.cancel()
|
if self.task_start:
|
||||||
self.MakeBackups.cancel()
|
self.CheckDataBases.cancel()
|
||||||
|
self.MakeBackups.cancel()
|
||||||
|
|
||||||
self.stop_event.set()
|
self.stop_event.set()
|
||||||
|
|
||||||
@@ -515,9 +519,14 @@ class MainBot(AnyBots):
|
|||||||
#/преды
|
#/преды
|
||||||
|
|
||||||
async def on_message(self, msg):
|
async def on_message(self, msg):
|
||||||
|
|
||||||
|
if msg.author.bot:
|
||||||
|
return 0
|
||||||
|
|
||||||
if msg.author.id == 479210801891115009 and msg.content == "botsoff":
|
if msg.author.id == 479210801891115009 and msg.content == "botsoff":
|
||||||
await msg.reply(embed=disnake.Embed(description=f'Бот отключён', colour=0xff9900))
|
await msg.reply(embed=disnake.Embed(description=f'Бот отключён', colour=0xff9900))
|
||||||
await self.BotOff()
|
await self.BotOff()
|
||||||
|
return 0
|
||||||
if type(msg.channel).__name__!="DMChannel" and fnmatch(msg.channel.name, "⚠жалоба-от-*-на-*"):
|
if type(msg.channel).__name__!="DMChannel" and fnmatch(msg.channel.name, "⚠жалоба-от-*-на-*"):
|
||||||
log_reports = disnake.utils.get(msg.guild.channels, id=1242373230384386068)
|
log_reports = disnake.utils.get(msg.guild.channels, id=1242373230384386068)
|
||||||
files=[]
|
files=[]
|
||||||
@@ -527,12 +536,38 @@ class MainBot(AnyBots):
|
|||||||
f"Автор: `{msg.author.name} ({msg.author.id})`\n" +
|
f"Автор: `{msg.author.name} ({msg.author.id})`\n" +
|
||||||
(f"Сообщение: ```{msg.content}```\n" if msg.content else ""),
|
(f"Сообщение: ```{msg.content}```\n" if msg.content else ""),
|
||||||
files = files)
|
files = files)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def extract_root_domain(url):
|
||||||
|
ext = tldextract.extract(url)
|
||||||
|
if not ext.domain or not ext.suffix:
|
||||||
|
return None
|
||||||
|
return f"{ext.domain}.{ext.suffix}".lower()
|
||||||
|
|
||||||
|
log = disnake.utils.get(msg.guild.channels, id=893065482263994378)
|
||||||
|
|
||||||
|
url_pattern = re.compile(r'https?://[^\s]+')
|
||||||
|
links = re.findall(url_pattern, msg.content)
|
||||||
|
аllowed_domains_model = self.DataBaseManager.model_classes['аllowed_domains']
|
||||||
|
async with self.DataBaseManager.session() as session:
|
||||||
|
for link in links:
|
||||||
|
root_domain = extract_root_domain(link)
|
||||||
|
stmt = self.DataBaseManager.select(аllowed_domains_model).where(аllowed_domains_model.domain == root_domain)
|
||||||
|
link_in_wl = (await session.execute(stmt)).scalars().first()
|
||||||
|
|
||||||
|
if link_in_wl is None:
|
||||||
|
print("Нарушение!!!")
|
||||||
|
await log.send(f"{msg.author.mention}({msg.author.id}) отправил в чат {msg.channel.mention} сомнительную ссылку, которой нет в вайлисте:```{msg.content}```")
|
||||||
|
mess = await msg.reply(embed=disnake.Embed(description=f'Этой ссылки нет в белом списке. Чтобы её туда добавили, свяжитесь с разработчиком или модераторами.', colour=0xff9900))
|
||||||
|
await msg.delete()
|
||||||
|
await asyncio.sleep(20)
|
||||||
|
await mess.delete()
|
||||||
|
return 1
|
||||||
|
|
||||||
message_words = msg.content.replace("/", " ").split(" ")
|
message_words = msg.content.replace("/", " ").split(" ")
|
||||||
if "discord.gg" in message_words:
|
if "discord.gg" in message_words:
|
||||||
for i in range(len(message_words)):
|
for i in range(len(message_words)):
|
||||||
if message_words[i]=="discord.gg" and not msg.author.bot:
|
if message_words[i]=="discord.gg" and not msg.author.bot:
|
||||||
log = disnake.utils.get(msg.guild.channels, id=893065482263994378)
|
|
||||||
try:
|
try:
|
||||||
inv = await self.fetch_invite(url = "https://discord.gg/"+message_words[i+1])
|
inv = await self.fetch_invite(url = "https://discord.gg/"+message_words[i+1])
|
||||||
if inv.guild.id != 490445877903622144:
|
if inv.guild.id != 490445877903622144:
|
||||||
@@ -562,51 +597,6 @@ async def init_db():
|
|||||||
|
|
||||||
return DatabaseManager(DataBaseEngine, DataBaseClasses)
|
return DatabaseManager(DataBaseEngine, DataBaseClasses)
|
||||||
|
|
||||||
async def db_migration(DB_MANAGER):
|
|
||||||
new_DataBase = DB_MANAGER
|
|
||||||
DataBase = await old_DatabaseManager.connect("data/penalties.db")
|
|
||||||
await DataBase.execute("PRAGMA journal_mode=WAL")
|
|
||||||
await DataBase.execute("PRAGMA synchronous=NORMAL")
|
|
||||||
await DataBase.execute("PRAGMA foreign_keys = ON")
|
|
||||||
try:
|
|
||||||
async with new_DataBase.engine.begin() as conn:
|
|
||||||
await conn.run_sync(new_DataBase.metadata.drop_all)
|
|
||||||
await conn.run_sync(new_DataBase.metadata.create_all)
|
|
||||||
async with new_DataBase.session() as session:
|
|
||||||
for penaltid, userid, reason, timend, timewarn in await DataBase.SelectBD('punishment_text_mutes'):
|
|
||||||
penault = DB_MANAGER.model_classes['punishment_mutes_text'](user_id = userid, reason = reason, time_end = timend if timend else None, time_warn = timewarn if timewarn else None)
|
|
||||||
async with session.begin():
|
|
||||||
session.add(penault)
|
|
||||||
|
|
||||||
for penaltid, userid, reason, timend, timewarn in await DataBase.SelectBD('punishment_voice_mutes'):
|
|
||||||
penault = DB_MANAGER.model_classes['punishment_mutes_voice'](user_id = userid, reason = reason, time_end = timend if timend else None, time_warn = timewarn if timewarn else None)
|
|
||||||
async with session.begin():
|
|
||||||
session.add(penault)
|
|
||||||
|
|
||||||
for penaltid, userid, reason, timend in await DataBase.SelectBD('punishment_bans'):
|
|
||||||
penault = DB_MANAGER.model_classes['punishment_bans'](user_id = userid, reason = reason, time_end = timend if timend else None)
|
|
||||||
async with session.begin():
|
|
||||||
session.add(penault)
|
|
||||||
|
|
||||||
for penaltid, userid, reason in await DataBase.SelectBD('punishment_perms'):
|
|
||||||
penault = DB_MANAGER.model_classes['punishment_perms'](user_id = userid, reason = reason)
|
|
||||||
async with session.begin():
|
|
||||||
session.add(penault)
|
|
||||||
|
|
||||||
for penaltid, userid, reason, timend in await DataBase.SelectBD('punishment_warns'):
|
|
||||||
penault = DB_MANAGER.model_classes['punishment_warns'](user_id = userid, reason = reason, time_warn = timend)
|
|
||||||
async with session.begin():
|
|
||||||
session.add(penault)
|
|
||||||
|
|
||||||
for penaltid, userid, reason, timend in await DataBase.SelectBD('punishment_reprimands'):
|
|
||||||
penault = DB_MANAGER.model_classes['punishment_reprimands'](user_id = userid, reason = reason, time_warn = timend)
|
|
||||||
async with session.begin():
|
|
||||||
session.add(penault)
|
|
||||||
finally:
|
|
||||||
await DataBase.close()
|
|
||||||
|
|
||||||
raise Exception("Миграция БД завершена. требуется переименовывание файлов")
|
|
||||||
|
|
||||||
async def run_bot(bot, token, stop_event):
|
async def run_bot(bot, token, stop_event):
|
||||||
try:
|
try:
|
||||||
await bot.start(token)
|
await bot.start(token)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import math
|
|||||||
import random
|
import random
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
|
import tldextract
|
||||||
|
|
||||||
translate = {"textmute":"Текстовый мут", "voicemute":"Голосовой мут", "ban":"Бан", "warning":"Предупреждение",\
|
translate = {"textmute":"Текстовый мут", "voicemute":"Голосовой мут", "ban":"Бан", "warning":"Предупреждение",\
|
||||||
"time":"Время", "reason":"Причина", "changenick":"Сменить ник", "reprimand":"Выговор", "newnick":"Новый ник"}
|
"time":"Время", "reason":"Причина", "changenick":"Сменить ник", "reprimand":"Выговор", "newnick":"Новый ник"}
|
||||||
@@ -683,4 +684,58 @@ class ModerModule(commands.Cog):
|
|||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
await ctx.send(embed = disnake.Embed(description = f'Данный пользователь не имеет роли в ветке {branchid}', colour = 0xff9900))
|
await ctx.send(embed = disnake.Embed(description = f'Данный пользователь не имеет роли в ветке {branchid}', colour = 0xff9900))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
@commands.slash_command(description="Позволяет добавить домен в белый список", name="добавить_ссылку", administrator=True)
|
||||||
|
async def add_domain(self, ctx: disnake.AppCmdInter, link: str = commands.Param(description="Укажите ссылку или домен", name="ссылка")):
|
||||||
|
async with self.DataBaseManager.session() as session:
|
||||||
|
async with session.begin():
|
||||||
|
staff_branches_model = self.DataBaseManager.model_classes['staff_branches']
|
||||||
|
аllowed_domains_model = self.DataBaseManager.model_classes['аllowed_domains']
|
||||||
|
|
||||||
|
admin_flag = False
|
||||||
|
|
||||||
|
stmt = (
|
||||||
|
self.DataBaseManager.select(staff_branches_model)
|
||||||
|
.options(
|
||||||
|
self.DataBaseManager.selectinload(staff_branches_model.users)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
self.DataBaseManager.or_(
|
||||||
|
staff_branches_model.is_admin == True,
|
||||||
|
staff_branches_model.is_moder == True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
branches = (await session.execute(stmt)).scalars().all()
|
||||||
|
|
||||||
|
for branch in branches:
|
||||||
|
for user in branch.users:
|
||||||
|
if ctx.author.id == user.user_id:
|
||||||
|
admin_flag = True
|
||||||
|
break
|
||||||
|
if admin_flag:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not admin_flag:
|
||||||
|
await ctx.send(embed = disnake.Embed(description = f'У вас недостаточно полномочий, чтобы добавлять ссылку в белый лист. Обратитесь к любому модератору или разработчику.', colour = 0xff9900))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def extract_root_domain(url):
|
||||||
|
ext = tldextract.extract(url)
|
||||||
|
if not ext.domain or not ext.suffix:
|
||||||
|
return None
|
||||||
|
return f"{ext.domain}.{ext.suffix}".lower()
|
||||||
|
new_link = link if not "http" in link else extract_root_domain(link)
|
||||||
|
if not new_link:
|
||||||
|
await ctx.send(embed = disnake.Embed(description = f'Некорректная ссылка!', colour = 0xff9900))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
domain = аllowed_domains_model(domain = new_link, initiator_id = ctx.author.id)
|
||||||
|
session.add(domain)
|
||||||
|
|
||||||
|
await ctx.send(embed = disnake.Embed(description = f'Домен {new_link} успешно добавлен в белый список', colour = 0xff9900))
|
||||||
return 1
|
return 1
|
||||||
@@ -200,7 +200,13 @@ class StaffCuration(Base):
|
|||||||
UniqueConstraint('apprentice_id', 'curator_id', 'branch_id', name='uq_apprentice_curator_branch'),
|
UniqueConstraint('apprentice_id', 'curator_id', 'branch_id', name='uq_apprentice_curator_branch'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class AllowedDomain(Base):
|
||||||
|
__tablename__ = "аllowed_domains"
|
||||||
|
id: Mapped[identificator_pk]
|
||||||
|
domain: Mapped[str] = mapped_column(Text, index=True, nullable=False, unique=True)
|
||||||
|
initiator_id: Mapped[discord_identificator]
|
||||||
|
|
||||||
all_data = {
|
all_data = {
|
||||||
'base': Base
|
'base': Base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,239 +0,0 @@
|
|||||||
try:
|
|
||||||
import aiosqlite
|
|
||||||
import disnake
|
|
||||||
from disnake.ext import commands
|
|
||||||
from disnake.ext import tasks
|
|
||||||
except:
|
|
||||||
import pip
|
|
||||||
|
|
||||||
pip.main(['install', 'disnake'])
|
|
||||||
pip.main(['install', 'aiosqlite'])
|
|
||||||
import disnake
|
|
||||||
from disnake.ext import commands
|
|
||||||
from disnake.ext import tasks
|
|
||||||
import aiosqlite
|
|
||||||
from typing import Optional, Union, List, Dict, Any, AsyncIterator, Tuple
|
|
||||||
from asyncio import Lock
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
class DatabaseManager:
|
|
||||||
"""Расширенное соединение с БД с поддержкой транзакций и удобных методов"""
|
|
||||||
|
|
||||||
def __init__(self, connection: aiosqlite.Connection):
|
|
||||||
self._connection = connection
|
|
||||||
self._transaction_depth = 0
|
|
||||||
self._closed = False
|
|
||||||
self._transaction_lock = Lock()
|
|
||||||
self.last_error = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def connect(cls, database: str, **kwargs) -> 'DatabaseManager':
|
|
||||||
"""Альтернатива конструктору для подключения"""
|
|
||||||
connection = await aiosqlite.connect(database, **kwargs)
|
|
||||||
return cls(connection)
|
|
||||||
|
|
||||||
async def UpdateBD(self, table: str, *, change: dict, where: dict, whereandor = "AND"):
|
|
||||||
request = ()
|
|
||||||
|
|
||||||
change_request = []
|
|
||||||
for i in change.keys():
|
|
||||||
change_request.append(f"{i} = ?")
|
|
||||||
request = request + (change[i],)
|
|
||||||
|
|
||||||
where_request = []
|
|
||||||
for i in where.keys():
|
|
||||||
where_request.append(f"{i} = ?")
|
|
||||||
request = request + (where[i],)
|
|
||||||
|
|
||||||
await self.execute('UPDATE {table} SET {change} WHERE {where}'
|
|
||||||
.format(table = table, change = ", ".join(change_request), where = f" {whereandor} ".join(where_request)), request)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
async def SelectBD(self, table: str, *, select: list = ["*"], where: dict = None, where_ops: dict = None, whereandor = "AND", order_by: str = None, limit: int = None):
|
|
||||||
|
|
||||||
where_combined = {}
|
|
||||||
if where:
|
|
||||||
where_combined.update({f"{k} =": v for k, v in where.items()})
|
|
||||||
if where_ops:
|
|
||||||
where_combined.update(where_ops)
|
|
||||||
|
|
||||||
request = ()
|
|
||||||
where_clauses = ""
|
|
||||||
|
|
||||||
for condition, value in where_combined.items():
|
|
||||||
field_op = condition.split()
|
|
||||||
field = field_op[0]
|
|
||||||
op = "=" if len(field_op) == 1 else field_op[1]
|
|
||||||
if where_clauses == "":
|
|
||||||
where_clauses= where_clauses + f"{field} {op} ? "
|
|
||||||
else:
|
|
||||||
where_clauses= where_clauses + f"{whereandor} {field} {op} ? "
|
|
||||||
request += (value,)
|
|
||||||
|
|
||||||
query = "SELECT {select} FROM {table}".format(
|
|
||||||
select=", ".join(select),
|
|
||||||
table=table
|
|
||||||
)
|
|
||||||
|
|
||||||
if where_clauses:
|
|
||||||
query += f" WHERE {where_clauses}"
|
|
||||||
|
|
||||||
if order_by:
|
|
||||||
query += f" ORDER BY {order_by}"
|
|
||||||
|
|
||||||
if limit:
|
|
||||||
query += f" LIMIT {limit}"
|
|
||||||
|
|
||||||
async with await self.execute(query, request) as cursor:
|
|
||||||
return [i for i in await cursor.fetchall()]
|
|
||||||
|
|
||||||
async def GetStaffJoins():
|
|
||||||
query = \
|
|
||||||
"""
|
|
||||||
SELECT sur.userid, sur.roleid, sur.description, sur.starttime, sr.staffsalary, sbr.layer as rolelayer, sbr.branchid, sb.layer as branchlayer, sb.purpose
|
|
||||||
FROM staff_users_roles AS sur
|
|
||||||
JOIN staff_roles as sr ON sr.roleid = sur.roleid
|
|
||||||
JOIN staff_branches_roles as sbr ON sbr.roleid = sur.roleid
|
|
||||||
JOIN staff_branches as sb ON sb.branchid = sbr.branchid
|
|
||||||
ORDER BY branchlayer ASC, rolelayer ASC;
|
|
||||||
"""
|
|
||||||
async with await self.execute(query, request) as cursor:
|
|
||||||
answer = [i for i in await cursor.fetchall()]
|
|
||||||
result = [{'userid': userid, 'roleid': roleid, 'description': description, 'starttime': starttime, 'staffsalary': staffsalary, 'rolelayer': rolelayer, 'branchid': branchid, 'branchlayer': branchlayer, 'purpose': purpose} for userid, roleid, description, starttime, staffsalary, rolelayer, branchid, branchlayer, purpose in answer]
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def DeleteBD(self, table: str, *, where: dict, whereandor = "AND"):
|
|
||||||
request = ()
|
|
||||||
where_request = []
|
|
||||||
for i in where.keys():
|
|
||||||
where_request.append(f"{i} = ?")
|
|
||||||
request = request + (where[i],)
|
|
||||||
await self.execute("DELETE FROM {table} where {where}"
|
|
||||||
.format(table = table, where = f" {whereandor} ".join(where_request)), request)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
async def InsertBD(self, table: str, *, data: dict):
|
|
||||||
request = ()
|
|
||||||
keys = list(data.keys())
|
|
||||||
qstring = []
|
|
||||||
for i in keys:
|
|
||||||
request = request + (data[i],)
|
|
||||||
qstring.append("?")
|
|
||||||
await self.execute("INSERT INTO {table}({keys}) VALUES({values})"
|
|
||||||
.format(table = table, keys = ", ".join(keys), values = ", ".join(qstring)), request)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
async def execute(self, sql: str, parameters: Optional[Union[Tuple, Dict]] = None, **kwargs) -> aiosqlite.Cursor:
|
|
||||||
"""
|
|
||||||
Универсальный execute, который автоматически определяет:
|
|
||||||
- Нужно ли начинать транзакцию (для INSERT/UPDATE/DELETE вне транзакции)
|
|
||||||
- Работает ли уже внутри транзакции (не создаёт вложенные транзакции)
|
|
||||||
"""
|
|
||||||
is_modifying = sql.strip().upper().startswith(("INSERT", "UPDATE", "DELETE"))
|
|
||||||
|
|
||||||
# Если это модифицирующий запрос И мы НЕ внутри транзакции
|
|
||||||
if is_modifying and self._transaction_depth == 0:
|
|
||||||
async with self: # Автоматические begin/commit
|
|
||||||
cursor = await self._connection.execute(sql, parameters or (), **kwargs)
|
|
||||||
await cursor.close() # Важно: закрываем курсор для COMMIT
|
|
||||||
return cursor
|
|
||||||
else:
|
|
||||||
# Для SELECT или работы внутри существующей транзакции
|
|
||||||
return await self._connection.execute(sql, parameters or (), **kwargs)
|
|
||||||
|
|
||||||
async def fetch_all(self, sql: str, parameters: Optional[Union[Tuple, Dict]] = None) -> List[Tuple]:
|
|
||||||
"""Выполняет запрос и возвращает все строки"""
|
|
||||||
async with await self.execute(sql, parameters) as cursor:
|
|
||||||
return await cursor.fetchall()
|
|
||||||
|
|
||||||
async def fetch_one(self, sql: str, parameters: Optional[Union[Tuple, Dict]] = None) -> Optional[Tuple]:
|
|
||||||
"""Выполняет запрос и возвращает первую строку"""
|
|
||||||
async with await self.execute(sql, parameters) as cursor:
|
|
||||||
return await cursor.fetchone()
|
|
||||||
|
|
||||||
async def fetch_val(self, sql: str, parameters: Optional[Union[Tuple, Dict]] = None, column: int = 0) -> Any:
|
|
||||||
"""Возвращает значение из первого столбца"""
|
|
||||||
row = await self.fetch_one(sql, parameters)
|
|
||||||
return row[column] if row else None
|
|
||||||
|
|
||||||
async def insert(self, table: str, data: Dict[str, Any], on_conflict: str = None) -> int:
|
|
||||||
"""Упрощенный INSERT с поддержкой ON CONFLICT"""
|
|
||||||
keys = data.keys()
|
|
||||||
values = list(data.values())
|
|
||||||
|
|
||||||
sql = f"""
|
|
||||||
INSERT INTO {table} ({', '.join(keys)})
|
|
||||||
VALUES ({', '.join(['?']*len(keys))})
|
|
||||||
"""
|
|
||||||
|
|
||||||
if on_conflict:
|
|
||||||
sql += f" ON CONFLICT {on_conflict}"
|
|
||||||
|
|
||||||
await self.execute(sql, values)
|
|
||||||
return self.lastrowid
|
|
||||||
|
|
||||||
async def update(self, table: str, where: Dict[str, Any], changes: Dict[str, Any], where_operator: str = "AND") -> int:
|
|
||||||
"""Упрощенный UPDATE с автоматическим построением WHERE"""
|
|
||||||
set_clause = ", ".join([f"{k} = ?" for k in changes.keys()])
|
|
||||||
where_clause = f" {where_operator} ".join([f"{k} = ?" for k in where.keys()])
|
|
||||||
|
|
||||||
sql = f"""
|
|
||||||
UPDATE {table}
|
|
||||||
SET {set_clause}
|
|
||||||
WHERE {where_clause}
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = await self.execute(sql, [*changes.values(), *where.values()])
|
|
||||||
return result.rowcount
|
|
||||||
|
|
||||||
async def begin(self):
|
|
||||||
"""Начать транзакцию (с поддержкой вложенности)"""
|
|
||||||
async with self._transaction_lock:
|
|
||||||
if self._transaction_depth == 0:
|
|
||||||
await self._connection.execute("BEGIN IMMEDIATE")
|
|
||||||
self._transaction_depth += 1
|
|
||||||
|
|
||||||
async def commit(self):
|
|
||||||
"""Зафиксировать транзакцию"""
|
|
||||||
async with self._transaction_lock:
|
|
||||||
if self._transaction_depth == 1:
|
|
||||||
await self._connection.commit()
|
|
||||||
self._transaction_depth = max(0, self._transaction_depth - 1)
|
|
||||||
|
|
||||||
async def rollback(self):
|
|
||||||
"""Откатить транзакцию"""
|
|
||||||
async with self._transaction_lock:
|
|
||||||
if self._transaction_depth > 0:
|
|
||||||
await self._connection.rollback()
|
|
||||||
self._transaction_depth = 0
|
|
||||||
|
|
||||||
async def close(self) -> None:
|
|
||||||
"""Безопасное закрытие соединения с учётом транзакций"""
|
|
||||||
async with self._transaction_lock:
|
|
||||||
try:
|
|
||||||
# Откатываем активную транзакцию, если есть
|
|
||||||
if self._transaction_depth > 0:
|
|
||||||
await self._connection.rollback()
|
|
||||||
self._transaction_depth = 0
|
|
||||||
|
|
||||||
# Закрываем соединение
|
|
||||||
if hasattr(self._connection, '_connection'): # Проверка внутреннего состояния
|
|
||||||
await self._connection.close()
|
|
||||||
except Exception as e:
|
|
||||||
self.last_error = f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Ошибка при закрытии соединения: {e}"
|
|
||||||
print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Ошибка при закрытии соединения: {e}")
|
|
||||||
finally:
|
|
||||||
# Помечаем соединение как закрытое
|
|
||||||
self._closed = True
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
|
||||||
await self.begin() # Используем собственный метод begin
|
|
||||||
return self # Возвращаем сам менеджер, а не соединение
|
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
if exc_type is None:
|
|
||||||
await self.commit()
|
|
||||||
else:
|
|
||||||
self.last_error = f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Во время записи в бд произошла ошибка: {exc_type}({exc_val}): {exc_tb.tb_frame.f_code.co_filename}(строка {exc_tb.tb_lineno})!"
|
|
||||||
print(f"{datetime.datetime.now().strftime('%H:%M:%S %d-%m-%Y')}:: Во время записи в бд произошла ошибка: {exc_type}({exc_val}): {exc_tb.tb_frame.f_code.co_filename}(строка {exc_tb.tb_lineno})!")
|
|
||||||
await self.rollback()
|
|
||||||
@@ -46,8 +46,8 @@ async def main():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
DataBase = await CoreMod.init_db()
|
DataBase = await CoreMod.init_db()
|
||||||
#sup_bot = CoreMod.MainBot(DataBase, stop_event)
|
sup_bot = CoreMod.MainBot(DataBase, stop_event, task_start = False)
|
||||||
sup_bot = CoreMod.AnyBots(DataBase)
|
#sup_bot = CoreMod.AnyBots(DataBase)
|
||||||
all_bots = [sup_bot]
|
all_bots = [sup_bot]
|
||||||
|
|
||||||
#НЕ СМЕЙ РАСКОММЕНТИРОВАТЬ
|
#НЕ СМЕЙ РАСКОММЕНТИРОВАТЬ
|
||||||
|
|||||||
Reference in New Issue
Block a user