Merge pull request #5 from MrSedan/dev

Added some features
This commit is contained in:
2023-11-22 21:03:03 +03:00
committed by GitHub
25 changed files with 271 additions and 569 deletions

View File

@@ -1 +0,0 @@
Generic single-database configuration.

View File

@@ -1,84 +0,0 @@
import os
from logging.config import fileConfig
from os import getenv
from os.path import dirname, join
from dotenv import load_dotenv
from sqlalchemy import create_engine
from alembic import context
from db.data import Base
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
load_dotenv(join(dirname(__file__), '..', '.env'))
DATABASE_PASSWORD=os.getenv('DATABASE_PASSWORD')
DATABASE_NAME=os.getenv('DATABASE_NAME')
DATABASE_USER=os.getenv('DATABASE_USER')
DATABASE_PORT=os.getenv('DATABASE_PORT')
DATABASE_HOST=os.getenv('DATABASE_HOST')
URL=f"postgresql+psycopg2://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}"
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = URL
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = create_engine(URL)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -1,26 +0,0 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}

View File

@@ -1,30 +0,0 @@
"""Added file_id to image table
Revision ID: 1d040fbb74ce
Revises: 8c2a92134271
Create Date: 2023-11-04 03:35:28.011123
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '1d040fbb74ce'
down_revision: Union[str, None] = '8c2a92134271'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('photo', sa.Column('file_id', sa.String(), nullable=False))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('photo', 'file_id')
# ### end Alembic commands ###

View File

@@ -1,32 +0,0 @@
"""Moved media group from image to post
Revision ID: 278f7650482f
Revises: ca01506184b5
Create Date: 2023-11-04 02:32:22.398760
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '278f7650482f'
down_revision: Union[str, None] = 'ca01506184b5'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('photo', 'media_group_id')
op.add_column('post', sa.Column('media_group_id', sa.Integer(), nullable=False))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('post', 'media_group_id')
op.add_column('photo', sa.Column('media_group_id', sa.INTEGER(), autoincrement=False, nullable=False))
# ### end Alembic commands ###

View File

@@ -1,30 +0,0 @@
"""Added timestamp and posted
Revision ID: 2af6a0df717b
Revises: 1d040fbb74ce
Create Date: 2023-11-04 04:09:12.689628
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '2af6a0df717b'
down_revision: Union[str, None] = '1d040fbb74ce'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('post', sa.Column('timestamp', sa.DateTime(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('post', 'timestamp')
# ### end Alembic commands ###

View File

@@ -1,36 +0,0 @@
"""Change to str mediag_group_id
Revision ID: 8c2a92134271
Revises: 278f7650482f
Create Date: 2023-11-04 03:06:58.339894
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '8c2a92134271'
down_revision: Union[str, None] = '278f7650482f'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('post', 'media_group_id',
existing_type=sa.INTEGER(),
type_=sa.String(),
existing_nullable=False)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('post', 'media_group_id',
existing_type=sa.String(),
type_=sa.INTEGER(),
existing_nullable=False)
# ### end Alembic commands ###

View File

@@ -1,30 +0,0 @@
"""Initial migration
Revision ID: 9de2db27ca6e
Revises:
Create Date: 2023-10-29 01:23:30.890347
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '9de2db27ca6e'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@@ -1,30 +0,0 @@
"""Added spoiler to photo
Revision ID: c9872bd4d4b5
Revises: f0ed48a3ded3
Create Date: 2023-11-15 23:38:03.886970
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'c9872bd4d4b5'
down_revision: Union[str, None] = 'f0ed48a3ded3'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('photo', sa.Column('has_spoiler', sa.Boolean(), nullable=False))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('photo', 'has_spoiler')
# ### end Alembic commands ###

View File

@@ -1,38 +0,0 @@
"""Added image table
Revision ID: ca01506184b5
Revises: 9de2db27ca6e
Create Date: 2023-11-04 02:21:28.077631
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'ca01506184b5'
down_revision: Union[str, None] = '9de2db27ca6e'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('photo',
sa.Column('message_id', sa.Integer(), nullable=False),
sa.Column('media_group_id', sa.Integer(), nullable=False),
sa.Column('post_id', sa.Uuid(), nullable=False),
sa.ForeignKeyConstraint(['post_id'], ['post.uuid'], ),
sa.PrimaryKeyConstraint('message_id')
)
op.drop_column('post', 'images')
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('post', sa.Column('images', sa.VARCHAR(), autoincrement=False, nullable=False))
op.drop_table('photo')
# ### end Alembic commands ###

View File

@@ -1,30 +0,0 @@
"""Added posted
Revision ID: f0ed48a3ded3
Revises: 2af6a0df717b
Create Date: 2023-11-04 04:12:46.479474
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'f0ed48a3ded3'
down_revision: Union[str, None] = '2af6a0df717b'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('post', sa.Column('posted', sa.Boolean(), nullable=False))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('post', 'posted')
# ### end Alembic commands ###

View File

@@ -1,78 +0,0 @@
import os
from datetime import datetime
from os.path import dirname, join
from typing import List, Optional
from uuid import UUID
from dotenv import load_dotenv
from sqlalchemy import Column, DateTime, ForeignKey, create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
load_dotenv(join(dirname(__file__), '..', '.env'))
DATABASE_PASSWORD = os.getenv('DATABASE_PASSWORD')
DATABASE_NAME = os.getenv('DATABASE_NAME')
DATABASE_USER = os.getenv('DATABASE_USER')
DATABASE_PORT = os.getenv('DATABASE_PORT')
DATABASE_HOST = os.getenv('DATABASE_HOST')
engine = create_engine(
f"postgresql+psycopg2://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}", echo=True)
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = 'user'
id: Mapped[int] = mapped_column(primary_key=True)
user_name: Mapped[Optional[str]]
admin: Mapped['Admin'] = relationship(
back_populates='user', cascade='all, delete-orphan')
def __repr__(self) -> str:
return f'User(id={self.id!r}, user_name={self.user_name!r})'
def __str__(self) -> str:
return f'User(id={self.id!r}, user_name={self.user_name!r})'
class Admin(Base):
__tablename__ = 'admin'
user_id: Mapped[int] = mapped_column(
ForeignKey('user.id'), primary_key=True)
user: Mapped['User'] = relationship(back_populates='admin')
posts: Mapped[List['Post']] = relationship(
back_populates='user', cascade='all, delete')
class Post(Base):
__tablename__ = 'post'
uuid: Mapped[UUID] = mapped_column(primary_key=True)
posted: Mapped[bool] = mapped_column(default=False)
from_user_id: Mapped[int] = mapped_column(ForeignKey('admin.user_id'))
user: Mapped['Admin'] = relationship(back_populates='posts')
text: Mapped[str]
media_group_id: Mapped[str]
images: Mapped[List['Image']] = relationship(
back_populates='post', cascade='all, delete')
timestamp = Column(DateTime, default=datetime.utcnow)
def __repr__(self) -> str:
return f"UUID: {self.uuid}. From_user_id: {self.from_user_id}. media group id: {self.media_group_id}. Text: {self.text}"
class Image(Base):
__tablename__ = 'photo'
message_id: Mapped[int] = mapped_column(primary_key=True)
post: Mapped['Post'] = relationship(back_populates='images')
post_id: Mapped[int] = mapped_column(ForeignKey('post.uuid'))
file_id: Mapped[str]
has_spoiler: Mapped[bool]
if __name__ == '__main__':
Base.metadata.create_all(engine)

View File

@@ -1,24 +1,22 @@
from datetime import datetime
from typing import Any
from uuid import uuid4
from aiogram import Bot, F, Router, types
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.utils.media_group import MediaGroupBuilder
from sqlalchemy.orm import Session
from db.data import Admin, Image, Post, User, engine
from handlers.filters.new_post import (ChangePosts, NewPostFilter,
NewSoloPostFilter)
from handlers.filters.reply_to_user import ReplyToUser
from handlers.middlewares.user import AdminMiddleware
from handlers.states.change_post import ChangePost
from neuroapi import neuroapi
def get_post_info(post: Post, post_id: int) -> str:
text = post.text
time = post.timestamp
from_user = post.from_user_id
def get_post_info(post: dict, post_id: int) -> str:
text = post["text"]
time = post["timestamp"]
from_user = post["from_user_id"]
s = f"""Индекс: {post_id}\nТекст: {text}\nВремя отправки: {time}\nОт: [id{from_user}](tg://user?id={from_user})""".replace('#', '\#').replace(
"_", "\_").replace('.', '\.').replace(',', '\,').replace('!', '\!').replace('-', '\-').replace(':', '\:')
return s
@@ -33,43 +31,26 @@ class Admin_commands:
self.router = Router()
self.router.message.middleware(AdminMiddleware())
@self.router.message(Command('test'))
async def test_command(message: types.Message):
with Session(engine) as session:
user = session.get(User, message.from_user.id)
await message.answer(str(user))
@self.router.message(NewPostFilter())
async def new_post(message: types.Message):
with Session(engine) as session:
post = session.query(Post).filter(
Post.media_group_id == message.media_group_id).first()
if post:
photo = Image(message_id=message.message_id,
post=post, file_id=message.photo[-1].file_id, has_spoiler=bool(message.has_media_spoiler))
session.add(photo)
session.commit()
else:
print('No posts anymore ;-(')
post = await neuroapi.post.get_by_media_group_id(message.media_group_id)
await neuroapi.image.add(post['uuid'], message.photo[-1].file_id, message.has_media_spoiler, message.message_id)
@self.router.message(Command('info'))
async def info_command(message: types.Message):
with Session(engine) as session:
posts = session.query(Post).filter(Post.posted == False).all()
admins = session.query(Admin).all()
posts = await neuroapi.post.get_will_post()
post_c = {}
for admin in admins:
post_c[str(admin.user_id)] = 0
for post in posts:
post_c[str(post.from_user_id)] += 1
if post['from_user_id'] not in post_c:
post_c[post['from_user_id']] = 1
else:
post_c[post['from_user_id']] += 1
await message.answer(str(post_c))
@self.router.message(ChangePosts())
async def change_post(message: types.Message, state: FSMContext):
with Session(engine) as session:
posts = session.query(Post).filter(
Post.posted == False).order_by(Post.timestamp.asc()).all()
if len(posts):
posts = await neuroapi.post.get_will_post()
if (posts):
await state.update_data(posts=posts, id=0)
select_btns = []
if len(posts) > 1:
@@ -83,15 +64,15 @@ class Admin_commands:
text='Отмена', callback_data='cancel')]
]
keyboard = types.InlineKeyboardMarkup(inline_keyboard=kb)
post = await neuroapi.post.get(posts[0]['uuid'])
images = MediaGroupBuilder(
caption=get_post_info(posts[0], 1))
for image in posts[0].images:
images.add_photo(
image.file_id, parse_mode='markdownv2')
mes = await message.answer_media_group(media=images.build())
caption=get_post_info(post, 1))
for image in sorted(post['images'], key=lambda x: x['message_id']):
images.add_photo(image['file_id'],
has_spoiler=image['has_spoiler'], parse_mode='markdownv2')
mes = await message.answer_media_group(images.build())
await state.update_data(edit_msg=mes[0].message_id)
await message.answer('Действия', reply_markup=keyboard)
# await message.answer(get_post_info(posts[0]), reply_markup=keyboard, parse_mode='markdownv2')
else:
await message.answer('Нет постов')
@@ -119,7 +100,8 @@ class Admin_commands:
]
keyboard = types.InlineKeyboardMarkup(inline_keyboard=kb)
await state.update_data(id=post_id)
await bot.edit_message_caption(caption=get_post_info(posts[post_id], post_id+1), chat_id=callback.message.chat.id, message_id=data['edit_msg'], parse_mode='markdownv2')
post = await neuroapi.post.get(posts[post_id]['uuid'])
await bot.edit_message_caption(caption=get_post_info(post, post_id+1), chat_id=callback.message.chat.id, message_id=data['edit_msg'], parse_mode='markdownv2')
await callback.message.edit_reply_markup(reply_markup=keyboard)
await callback.answer()
@@ -149,13 +131,13 @@ class Admin_commands:
return
posts = data['posts']
post_id = data['id']
post: Post = posts[post_id]
with Session(engine) as session:
p = session.get(Post, post.uuid)
p.text = message.text
session.commit()
await state.clear()
post_uuid = posts[post_id]['uuid']
try:
await neuroapi.post.edit_text(post_uuid, message.text)
await message.answer(f'Текст поста изменен на: {message.text}')
except:
await message.answer('Ошибка')
await state.clear()
@self.router.callback_query(F.data == 'prev_post')
async def prev_post_changing(callback: types.CallbackQuery, state: FSMContext):
@@ -181,7 +163,8 @@ class Admin_commands:
]
keyboard = types.InlineKeyboardMarkup(inline_keyboard=kb)
await state.update_data(id=post_id)
await bot.edit_message_caption(caption=get_post_info(posts[post_id], post_id), chat_id=callback.message.chat.id, message_id=data['edit_msg'], parse_mode='markdownv2')
post = await neuroapi.post.get(posts[post_id]['uuid'])
await bot.edit_message_caption(caption=get_post_info(post, post_id), chat_id=callback.message.chat.id, message_id=data['edit_msg'], parse_mode='markdownv2')
await callback.message.edit_reply_markup(reply_markup=keyboard)
await callback.answer()
@@ -196,34 +179,34 @@ class Admin_commands:
@self.router.message(Command('post'))
async def post(message: types.Message):
with Session(engine) as session:
post = session.query(Post).filter(
Post.posted == False).order_by(Post.timestamp.asc()).first()
if post:
images = MediaGroupBuilder(caption=post.text)
for image in post.images[::-1]:
images.add_photo(
image.file_id, has_spoiler=image.has_spoiler)
posts = await neuroapi.post.get_will_post()
if (posts):
post = await neuroapi.post.get(posts[0]['uuid'])
images = MediaGroupBuilder(caption=post['text'])
for image in sorted(post['images'], key=lambda x: x['message_id']):
images.add_photo(image['file_id'],
has_spoiler=image['has_spoiler'])
await message.answer_media_group(images.build())
post.posted = True
session.commit()
else:
await message.answer('Постов немаэ')
await message.answer('Нет постов')
@self.router.message(NewSoloPostFilter())
async def post_solo(message: types.Message):
with Session(engine) as session:
post = Post(uuid=uuid4(), text=message.caption.replace(
'/newpost ', ''), media_group_id='')
post_user = session.get(Admin, message.from_user.id)
post.user = post_user
photo = Image(message_id=message.message_id,
post=post, file_id=message.photo[-1].file_id, has_spoiler=bool(message.has_media_spoiler))
session.add(photo)
session.add(post)
session.commit()
post = await neuroapi.post.new(message.caption.replace('/newpost ', ''), message.from_user.id)
await neuroapi.image.add(post['uuid'], message.photo[-1].file_id, message.has_media_spoiler, message.message_id)
await message.answer('Пост успешно добавлен!')
@self.router.message(ReplyToUser())
async def reply_user(message: types.Message):
if message.reply_to_message.forward_from is None:
await message.reply('Пользователь стесняшка и не разрешает отвечать на его сообщения...')
else:
try:
await bot.send_message(message.reply_to_message.forward_from.id, f'Вам ответил админ:\n{message.text}')
await message.reply('Ваше сообщение было отправлено!')
except Exception as e:
print(e)
def __call__(self, *args: Any, **kwds: Any) -> Router:
return self.router

View File

@@ -1,39 +1,28 @@
from asyncio import create_task
from time import sleep
from typing import Any
from uuid import uuid4
from aiogram import types
from aiogram.filters import Filter
from sqlalchemy.orm import Session
from db.data import Admin, Image, Post, User, engine
from neuroapi import neuroapi
class NewPostFilter(Filter):
async def __call__(self, message: types.Message) -> bool:
if message.media_group_id is None or message.content_type != 'photo':
return False
with Session(engine) as session:
post = session.query(Post).filter(
Post.media_group_id == message.media_group_id).first()
if post is None:
try:
await neuroapi.post.get_by_media_group_id(message.media_group_id)
except:
if not (message.caption.startswith('/newpost ') if message.caption else False):
return False
new_post = Post(uuid=uuid4(), text=message.caption.replace(
'/newpost ', ''), media_group_id=message.media_group_id)
post_user = session.get(Admin, message.from_user.id)
new_post.user = post_user
session.add(new_post)
session.commit()
await neuroapi.post.new(message.caption.replace(
'/newpost ', ''), str(message.from_user.id), str(message.media_group_id))
await message.answer('Пост успешно добавлен!')
return True
class NewSoloPostFilter(Filter):
async def __call__(self, message: types.Message) -> bool:
return message.media_group_id is None and message.content_type == 'photo' and message.caption.startswith('/newpost ')
return message.media_group_id is None and message.content_type == 'photo' and message.caption and message.caption.startswith('/newpost ')
class ChangePosts(Filter):
async def __call__(self, message: types.Message) -> bool:

View File

@@ -0,0 +1,9 @@
from aiogram import types
from aiogram.filters import Filter
class ReplyToUser(Filter):
async def __call__(self, message: types.Message) -> bool:
if message.reply_to_message is None or message.chat.type != 'private':
return False
return True

View File

@@ -2,9 +2,8 @@ from typing import Any, Awaitable, Callable, Dict
from aiogram import BaseMiddleware
from aiogram.types import Message
from sqlalchemy.orm import Session
from db.data import Admin, User, engine
from neuroapi import neuroapi
class AdminMiddleware(BaseMiddleware):
@@ -12,12 +11,8 @@ class AdminMiddleware(BaseMiddleware):
pass
async def __call__(self, handler: Callable[[Message, Dict[str, Any]], Awaitable[Any]], event: Message, data: Dict[str, Any]) -> Any:
with Session(engine) as session:
if not session.get(User, event.from_user.id):
user = User(id=event.from_user.id, user_name=event.from_user.username)
session.add(user)
session.commit()
isAdmin = session.get(Admin, event.from_user.id)
await neuroapi.user.get(str(event.from_user.id), event.from_user.username)
isAdmin = await neuroapi.admin.is_admin(str(event.from_user.id))
if not isAdmin:
await event.answer('Команда только для админов!')
return None

33
handlers/user_commands.py Normal file
View File

@@ -0,0 +1,33 @@
from typing import Any
from aiogram import Bot, F, Router, types
from neuroapi import neuroapi
class User_commands:
bot: Bot
router: Router
def __init__(self, bot: Bot) -> None:
self.bot = bot
self.router = Router()
@self.router.message(F.chat.type == 'private')
async def forward_post(message: types.Message):
admins = await neuroapi.admin.get()
canReply = True
for a in admins:
await bot.send_message(a['user_id'], f'Вам новое сообщение от пользователя {message.from_user.full_name}. ' +
(f'\nНик: @{message.from_user.username}' if message.from_user.username else f'ID: {message.from_user.id}'))
forwarded_message = await bot.forward_message(a['user_id'], message.chat.id, message.message_id)
if forwarded_message.forward_from is None:
canReply = False
await message.reply('Ваше сообщение было отправлено администраторам'+('' if canReply else '\nНо они не смогут вам ответить из-за ваших настроек конфиденциальности.'))
def __call__(self, *args: Any, **kwds: Any) -> Router:
return self.router
def setup(bot: Bot) -> Router:
return User_commands(bot)()

17
neuroapi/__init__.py Normal file
View File

@@ -0,0 +1,17 @@
from .post import Post
from .admin import Admin
from .user import User
from .image import Image
from dotenv import load_dotenv
import os
from os.path import join, dirname
load_dotenv(join(dirname(__file__), "..", '.env'))
class neuroapi:
post = Post(os.environ.get('API_URL'))
admin = Admin(os.environ.get('API_URL'))
user = User(os.environ.get('API_URL'))
image = Image(os.environ.get('API_URL'))

18
neuroapi/admin.py Normal file
View File

@@ -0,0 +1,18 @@
from aiohttp import ClientSession
from .api_method import ApiMethod
class Admin(ApiMethod):
async def get(self):
async with ClientSession() as session:
response = await session.get(self.api_url+'/admin/get')
return await response.json()
async def is_admin(self, id: str):
async with ClientSession() as session:
response = await session.get(self.api_url+f'/admin/is-admin/{id}')
if await response.text() == 'false':
return False
return True

5
neuroapi/api_method.py Normal file
View File

@@ -0,0 +1,5 @@
class ApiMethod:
api_url: str
def __init__(self, api_url: str) -> None:
self.api_url = api_url

View File

@@ -0,0 +1 @@
from .get_all import EGetAll

View File

@@ -0,0 +1,7 @@
from enum import Enum
class EGetAll(Enum):
all = 'all'
will_post = 'will-post'
posted = 'posted'

20
neuroapi/image.py Normal file
View File

@@ -0,0 +1,20 @@
import json
from aiohttp import ClientSession
from .api_method import ApiMethod
class Image(ApiMethod):
async def add(self, post_id: str, file_id: str, has_spoiler: bool | None, message_id: int):
payload = {'post_id': post_id, 'file_id': file_id,
'has_spoiler': has_spoiler, 'message_id': message_id}
if has_spoiler is None:
payload.pop('has_spoiler')
payload = json.dumps(payload)
async with ClientSession() as session:
response = await session.post(
self.api_url+'/image/add', data=payload, headers={'Content-Type': 'application/json'})
data = await response.json()
if 'statusCode' in data:
raise Exception(data['message'])

57
neuroapi/post.py Normal file
View File

@@ -0,0 +1,57 @@
import requests
from aiohttp import ClientSession
from .api_method import ApiMethod
from .enums import EGetAll
class Post(ApiMethod):
async def new(self, text: str, from_user_id: str, media_group_id: str = "None"):
payload = {'text': text, 'from_user_id': from_user_id}
if media_group_id != 'None':
payload['media_group_id'] = media_group_id
response = requests.post(self.api_url+'/post/new', data=payload)
data = response.json()
if 'statusCode' in data:
raise Exception(data['message'])
return data
async def __get_all(self, status: EGetAll):
async with ClientSession() as session:
response = await session.get(self.api_url+f'/post/get-all/{status.value}')
return response
async def get_all(self):
result = await self.__get_all(EGetAll.all)
return await result.json()
async def get_will_post(self):
result = await self.__get_all(EGetAll.will_post)
return await result.json()
async def get_posted(self):
result = await self.__get_all(EGetAll.posted)
return await result.json()
async def get(self, post_id: str):
async with ClientSession() as session:
response = await session.get(self.api_url+f'/post/get/{post_id}')
data = await response.json()
if 'statusCode' in data:
raise Exception(data['message'])
return data
async def get_by_media_group_id(self, media_group_id: str):
response = requests.get(self.api_url+f'/post/get-by-media-group-id/{media_group_id}')
data = response.json()
if 'statusCode' in data:
raise Exception(data['message'])
return data
async def edit_text(self, post_id: str, text: str):
response = requests.post(self.api_url+f"/post/edit/{post_id}", data={"text": text})
data = response.json()
if 'statusCode' in data:
raise Exception(data['message'])
return data

13
neuroapi/user.py Normal file
View File

@@ -0,0 +1,13 @@
from aiohttp import ClientSession
from .api_method import ApiMethod
class User(ApiMethod):
async def get(self, id: str, username: str):
payload = {'id': id, 'username': username}
async with ClientSession() as session:
response = await session.post(
self.api_url+'/user/get', data=payload)
data = await response.json()
if 'statusCode' in data:
raise Exception(data['message'])