Add filter available_roles

This commit is contained in:
hok7z 2022-08-16 13:28:08 +03:00
parent f6e2653daf
commit c9954c64ff
23 changed files with 244 additions and 308 deletions

View File

@ -4,7 +4,7 @@ api_id = ""
api_hash = ""
group_id = ""
log_group_id = ""
second_group_id = ""
vt_api = ""

View File

@ -32,7 +32,7 @@ Logging admin command actions in database.
| `db_url` | Connection info to database |
| `api_id` and `api_hash` | Telegram application data |
| `group_id` | Group id |
| `telegram_log_chat` | Seconds group for admins |
| `second_group_id` | Seconds group for admins |
| `vt_api` | VirusTotalAPI token (optionaly) |
## TODO

6
app.py
View File

@ -4,10 +4,16 @@ from aiogram import executor
from database import models
from load import dp, bot
import filters
dp.filters_factory.bind(filters.AvaibleRolesFilter)
dp.filters_factory.bind(filters.ReplayMessageFilter)
import handlers
import config
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
WEBAPP_HOST = '127.0.0.1'

View File

@ -1,5 +1,3 @@
import json
from aiogram import Dispatcher,Bot
from environs import Env
@ -12,7 +10,7 @@ use_webhook = True
token = env.str("bot_token")
group_id = env.str("group_id")
telegram_log_chat_id = env.str("log_group_id")
second_group_id = env.str("second_group_id")
# Telegram Application
api_id = env.int("api_id")
@ -21,8 +19,16 @@ api_hash = env.str("api_hash")
# Virus Total API
vt_api = env.str("vt_api")
with open("config/roles.json","r") as jsonfile:
roles = json.load(jsonfile)
group_permissions = {
"can_send_messages":True,
"can_send_media_messages":False,
"can_send_other_messages":True,
"can_send_polls":False,
"can_invite_users":False,
"can_change_info":False,
"can_add_web_page_previews":False,
"can_pin_messages":False
}
db_url = env.str("db_url")

View File

@ -1,72 +0,0 @@
{
"level": {
"owner": 3,
"admin": 2,
"helper": 1,
"member": 0
},
"group_permissions": {
"can_send_messages": true,
"can_send_media_messages": true,
"can_send_other_messages": true,
"can_send_polls": false,
"can_invite_users": false,
"can_change_info": false,
"can_add_web_page_previews": false,
"can_pin_messages": false
},
"roles": {
"owner": {
"ban": true,
"kick": true,
"mute": true,
"umute": true,
"warn": true,
"pin": true,
"srole": true,
"media": true,
"stickers": true,
"ro": true,
"reload": true
},
"admin": {
"ban": true,
"kick": true,
"mute": true,
"umute": true,
"warn": true,
"pin": true,
"srole": true,
"media": true,
"stickers": true,
"ro": true,
"reload": true
},
"helper": {
"ban": false,
"kick": true,
"mute": false,
"umute": false,
"warn": true,
"pin": false,
"srole": false,
"media": true,
"stickers": true,
"ro": false,
"reload": true
},
"member": {
"ban": false,
"kick": false,
"mute": false,
"umute": false,
"warn": false,
"pin": false,
"srole": false,
"media": false,
"stickers": false,
"ro": false,
"reload": false
}
}
}

View File

@ -86,7 +86,7 @@ class Database:
return False
for i in range(len(newvalues)):
query = Member.update({fieldnames[i]:newvalues[i]}).where(Member.user_id == user_id)
query = Member.update({fieldnames[i]:newvalues[i]}).where(Member.user_id == user_id).execute()
if (query is None):
return False

View File

@ -5,6 +5,14 @@ from playhouse.db_url import connect
from datetime import datetime, date
from enum import Enum
class MemberRoles(Enum):
OWNER = "owner"
ADMIN = "admin"
HELPER = "helper"
MEMBER = "member"
db = connect(config.db_url)
class Member(Model):
@ -22,7 +30,6 @@ class Member(Model):
database = db
class Restriction(Model):
restriction_id = BigIntegerField()
operation = CharField()
from_admin = ForeignKeyField(Member,lazy_load=False)

View File

@ -1 +1,2 @@
from .filters import IsAdminFilter,ReplayMessageFilter,UserHasRights
from .avaible_roles import AvaibleRolesFilter
from .replay_message import ReplayMessageFilter

27
filters/avaible_roles.py Normal file
View File

@ -0,0 +1,27 @@
from aiogram import types
from aiogram.dispatcher.filters import BoundFilter
from database.database import Member
from database.models import MemberRoles
from load import database
class AvaibleRolesFilter(BoundFilter):
"""Filter accessed roles"""
key = "available_roles"
def __init__(self,available_roles:list[MemberRoles]):
self.avaible_roles = available_roles
async def check(self,message:types.Message):
member = database.search_single_member(Member.user_id,message.from_user.id)
if (member.role == "owner"):
return True
status = member.role in [role.value for role in self.avaible_roles]
if (not status):
await message.answer("Command not avaible")
return status

View File

@ -1,77 +0,0 @@
from aiogram import types
from aiogram.dispatcher.filters import BoundFilter
# from config import roles
from database.database import Member
class IsAdminFilter(BoundFilter):
"""Check admin permission on hadler"""
key = 'is_admin'
def __init__(self, is_admin):
self.is_admin = is_admin
async def check(self, message: types.Message):
member = await message.bot.get_chat_member(message.chat.id, message.from_user.id)
result = member.is_chat_admin()
if not result:
await message.reply("🔒This command can only be used by an admin!")
return result
class UserHasRights(BoundFilter):
"""Check command in user rights"""
key = 'hasRights'
def __init__(self,hasRights):
self.hasRights = hasRights
async def check(self,message:types.Message):
import config
from load import database
roles = config.roles["roles"]
command = message.text.split()[0].lstrip("!")
user = database.search_single_member(Member.user_id,message.from_user.id)
# If data not exists,return False
if (user is None):
return False
# If role not exist,return False.
if not (user.role in roles.keys()):
return False
can_run_it = roles[user.role][command]
replied = message.reply_to_message
if (replied):
if (replied.from_user.id == message.from_user.id):
await message.answer("❌ You can't ")
return False
if (str(replied.from_user.id) == config.token.split(":")[0]):
await message.answer("You can't restrict bot.")
return False
if not (can_run_it):
await message.answer("You can't use this command.")
return False
return roles[user.role][command]
class ReplayMessageFilter(BoundFilter):
"""Check if message replied"""
key = 'replied'
def __init__(self, replied):
self.replied = replied
async def check(self, message: types.Message):
if message.reply_to_message is None:
await message.reply("Is command must be reply")
return False
return True

15
filters/replay_message.py Normal file
View File

@ -0,0 +1,15 @@
from aiogram import types
from aiogram.dispatcher.filters import BoundFilter
class ReplayMessageFilter(BoundFilter):
"""Check if message replied"""
key = 'replied'
def __init__(self, replied):
self.replied = replied
async def check(self, message: types.Message):
if message.reply_to_message is None:
await message.reply("Is command must be reply")
return False
return True

View File

@ -1,5 +1,4 @@
from . import groups
from . import private
from . import channels
from . import event
from . import errors
from . import groups
from . import users
from . import channels

View File

@ -15,9 +15,9 @@ async def errors_handler(update, exception):
await update.message.answer("Error happaned!\nBot terminated!")
await bot.send_message(
config.telegram_log_chat_id,
config.second_group_id,
f"**Bot terminated**!\nException:{exception}",
parse_mode="Markdown"
)
logging.info(f"Bot terminated!Exception:{exception}")
logging.info(f"Bot terminated!")

View File

@ -1,29 +0,0 @@
from load import dp, bot, types
# TODO: fix it
# import utils
# import config
# vt = utils.VirusTotalAPI(config.vt_api,True)
# @dp.message_handler(content_types=["document"],chat_type=[types.ChatType.SUPERGROUP])
# async def file_handler(message:types.Message):
# file = await bot.get_file(message.document.file_id)
#
# await bot.send_message(
# message.chat.id,
# await vt.scan_file(file.file_path),
# parse_mode="Markdown"
# )
@dp.message_handler()
async def filter_link_shorts(message:types.Message):
link_shorters = open("txt/link_shorters.txt","r").read().split()
for y in link_shorters:
for user_message in message.text.lower().split():
if (y in user_message):await message.delete()
# Joke
@dp.message_handler(content_types=types.ContentType.VOICE)
async def voice_message(message:types.Message):
photo = types.InputFile(path_or_bytesio="media/photo.jpg")
await message.answer_photo(photo)

View File

@ -1,2 +1,3 @@
from . import admin
from . import moderator
from . import user
from . import service

View File

@ -1,23 +1,23 @@
from load import bot, dp, types
from load import bot, database, dp, types
from aiogram.types.chat_permissions import ChatPermissions
import config
import utils
from load import database
from database.models import Member
import re
import json
from dataclasses import dataclass
from database.models import MemberRoles
def getArgument(arguments:list,index:int=0) -> str | None:
""" Get element from a list.If element not exist return None """
if not (arguments):
return None
if (len(arguments) >= index):
if (len(arguments) > index):
return arguments[index]
else:
return None
@ -30,10 +30,9 @@ class CommandArguments:
async def getCommandArgs(message:types.Message) -> CommandArguments:
""" Describe user data and arguments from message """
#Example:
#1.!command @username ... (not reply)
#2.!command (not_reply)
#3.!command ... (not reply)
"""
!command (@username/id) reason=None
"""
arguments_list = message.text.split()[1:]
@ -68,23 +67,36 @@ def checkArg(message:str) -> bool | None:
if (not message):
return None
argument = message.split()[1]
argument = message.split()
argument = getArgument(message.split(),1)
if (argument is None):
return None
on = ['enable','on','true']
off = ['disable','off','false']
return (argument in on) or (not argument in off)
if (argument in on):
return True
if (argument in off):
return False
def delete_substring_from_string(string:str,substring:str) -> str:
string_list = string.split(substring)
return "".join(string_list).lstrip()
# Filters:
# is_admin=True - Check admin permission, if user is admin, continue
# replied=True - If message is answer, continue
# is_admin=True - Check admin permission, if user is admin, continue.
# replied=True - If message is answer, continue.
# accessed_roles - list roles.
@dp.message_handler(commands=["ban"],commands_prefix="!",hasRights=True)
@dp.message_handler(commands=["ban"],commands_prefix="!",
available_roles=[MemberRoles.ADMIN,MemberRoles.HELPER])
async def ban_user(message: types.Message):
"""
!ban (@username/id) reason=None
"""
command = await getCommandArgs(message)
reason = getArgument(command.arguments)
@ -94,7 +106,7 @@ async def ban_user(message: types.Message):
# If can't descibe user data
if (user is None):
await message.answer((
"Usage:!ban @username reason=None"
"Usage:!ban (@username|id) reason=None.\n"
"Reply to a message or use with a username.")
)
return
@ -110,17 +122,22 @@ async def ban_user(message: types.Message):
database.delete_user(user.user_id)
# Open restrict
database.create_restriction(user.user_id, admin.id, "ban", reason)
database.create_restriction(admin.id, user.user_id, "ban", reason)
@dp.message_handler(commands=["unban"],commands_prefix="!",hasRights=True)
@dp.message_handler(commands=["unban"],commands_prefix="!",
available_roles=[MemberRoles.ADMIN,MemberRoles.HELPER])
async def unban_user(message: types.Message):
"""
!unban (@username/id) reason=None
"""
command = await getCommandArgs(message)
user = command.user
# If can't descibe user data
if (user is None):
await message.answer((
"Usage:!unban @username reason=None\n"
"Usage:!unban (@username|id) reason=None.\n"
"Reply to a message or use with username/id.")
)
return
@ -135,8 +152,14 @@ async def unban_user(message: types.Message):
await message.answer(f"User [{user.first_name}](tg://user?id={user.user_id}) has been unbaned.",
parse_mode="Markdown")
@dp.message_handler(commands=["kick"],commands_prefix="!",hasRights=True)
@dp.message_handler(commands=["kick"],commands_prefix="!",
available_roles=[MemberRoles.HELPER,MemberRoles.ADMIN])
async def kick_user(message:types.Message):
"""
!kick (@username/id) reason=None
"""
command = await getCommandArgs(message)
arguments = command.arguments
@ -147,7 +170,7 @@ async def kick_user(message:types.Message):
if (user is None):
await message.answer((
"Usage:!kick @username reason=None\n"
"Usage:!kick (@username|id) reason=None.\n"
"Reply to a message or use with a username/id.")
)
return
@ -160,10 +183,16 @@ async def kick_user(message:types.Message):
await message.answer(f"User [{user.first_name}](tg://user?id={user.user_id}) has been kicked.",
parse_mode="Markdown")
database.create_restriction(user.user_id,admin.id,"kick",reason)
database.create_restriction(admin.id,user.user_id,"kick",reason)
@dp.message_handler(commands=["mute"],commands_prefix="!",hasRights=True)
@dp.message_handler(commands=["mute"],commands_prefix="!",
available_roles=[MemberRoles.ADMIN])
async def mute_user(message:types.Message):
"""
!mute (@username/id) reason=None
"""
command = await getCommandArgs(message)
arguments = command.arguments
@ -172,7 +201,7 @@ async def mute_user(message:types.Message):
if (user is None):
await message.answer((
"Usage:!mute @username time\n"
"Usage:!mute (@username|id) [duration].\n"
"Reply to a message or use with a username/id.")
)
return
@ -201,8 +230,13 @@ async def mute_user(message:types.Message):
database.create_restriction(user.user_id, admin.id, "mute", reason)
@dp.message_handler(commands=["umute"],commands_prefix="!",hasRights=True)
@dp.message_handler(commands=["umute"],commands_prefix="!",
available_roles=[MemberRoles.ADMIN])
async def umute_user(message: types.Message):
"""
!umute (@username/id) reason=None
"""
# Get information
command = await getCommandArgs(message)
user = command.user
@ -210,13 +244,13 @@ async def umute_user(message: types.Message):
# If can't
if (user is None):
await message.answer((
"Usage:!unmute @username reason=None\n"
"Usage:!unmute (@username|id) reason=None.\n"
"Reply to a message or use with a username/id.")
)
return
# Get chat permissions
group_permissions = config.roles["group_permissions"]
group_permissions = config.group_permissions
# Set permissions
permissions = ChatPermissions(
@ -241,12 +275,17 @@ async def umute_user(message: types.Message):
await message.answer(f"User [{user.first_name}](tg://user?id={user.user_id}) has been unmuted.",
parse_mode="Markdown")
@dp.message_handler(commands=["pin"],commands_prefix="!",hasRights=True)
@dp.message_handler(commands=["pin"],commands_prefix="!",
available_roles=[MemberRoles.ADMIN,MemberRoles.HELPER])
async def pin_message(message:types.Message):
await bot.pin_chat_message(message.chat.id, message.reply_to_message.message_id)
@dp.message_handler(commands=["ro"],commands_prefix="!",hasRights=True)
@dp.message_handler(commands=["readonly","ro"],commands_prefix="!",
available_roles=[MemberRoles.ADMIN])
async def readonly_mode(message:types.Message):
"""
!ro/!readonly (@username/id)
"""
check = checkArg(message.text)
if (check is None):
@ -254,7 +293,7 @@ async def readonly_mode(message:types.Message):
return
# Get chat permissions
group_permissions = config.roles["group_permissions"]
group_permissions = config.group_permissions
# Set permissions
if (check):
@ -278,7 +317,9 @@ async def readonly_mode(message:types.Message):
if (status):
await message.answer(f"readonly - {check}")
@dp.message_handler(commands=["media"],commands_prefix="!",hasRights=True)
@dp.message_handler(commands=["media"],commands_prefix="!",
available_roles=[MemberRoles.ADMIN,MemberRoles.HELPER])
async def media_content(message: types.Message):
check = checkArg(message.text)
@ -287,7 +328,7 @@ async def media_content(message: types.Message):
return
# Get chat permissions
group_permissions = config.roles["group_permissions"]
group_permissions = config.group_permissions
# Set permissions
chat_permissions = ChatPermissions(
@ -307,8 +348,10 @@ async def media_content(message: types.Message):
if status:
await message.answer(f"media - {check}.")
@dp.message_handler(commands=["stickers"],commands_prefix="!",hasRights=True)
async def send_stickes(message: types.Message):
@dp.message_handler(commands=["stickers"],commands_prefix="!",
available_roles=[MemberRoles.ADMIN,MemberRoles.HELPER])
async def send_stickers(message: types.Message):
# Get arguments
check = checkArg(message.text)
@ -317,7 +360,7 @@ async def send_stickes(message: types.Message):
return
# Get chat permissions
group_permissions = config.roles["group_permissions"]
group_permissions = config.group_permissions
# Set permissions.
chat_permissions = ChatPermissions(
@ -337,7 +380,9 @@ async def send_stickes(message: types.Message):
if status:
await message.answer(f"stickes - {check}.")
@dp.message_handler(commands=["warn"],commands_prefix="!",hasRights=True)
@dp.message_handler(commands=["w","warn"],commands_prefix="!",
available_roles=[MemberRoles.ADMIN,MemberRoles.HELPER])
async def warn_user(message: types.Message):
# Get information
command = await getCommandArgs(message)
@ -348,7 +393,7 @@ async def warn_user(message: types.Message):
if (user is None):
await message.answer((
"Usage:!warn @username reason=None\n"
"Usage:!warn (@username/id) reason=None.\n"
"Reply to a message or use with username/id.")
)
return
@ -361,48 +406,37 @@ async def warn_user(message: types.Message):
database.create_restriction(user.user_id, admin.id, "warn", reason)
@dp.message_handler(commands=["reload"],commands_prefix="!")
@dp.message_handler(commands=["reload"],commands_prefix="!",available_roles=[MemberRoles.ADMIN,MemberRoles.HELPER])
async def reload(message:types.Message):
await utils.check_user_data()
group = await bot.get_chat(message.chat.id)
group_permissions = dict(group["permissions"])
with open("config/roles.json","r") as jsonfile:
data = json.load(jsonfile)
if group_permissions.keys() != data["group_permissions"].keys():
await message.answer("Add some permissions to roles.json")
return
for permission in group_permissions.keys():
data["group_permissions"][permission] = group_permissions[permission]
with open("config/roles.json", "w") as jsonfile:
json.dump(data, jsonfile,indent=4)
config.group_permissions[permission] = group_permissions[permission]
await message.answer(f"✅ The synchronization was successful.")
@dp.message_handler(commands=["srole"],commands_prefix="!",hasRights=True)
@dp.message_handler(commands=["set_role"],commands_prefix="!",
available_roles=[MemberRoles.ADMIN])
async def set_role(message:types.Message):
command = await getCommandArgs(message)
new_role = getArgument(command.arguments)
roles = config.roles
user = command.user
admin = database.search_single_member(Member.user_id,message.from_user)
if (admin is None):
return
if (user is None) or (new_role is None):
await message.answer("""
!srole @username/id role(owner,admin,helper,member)
Reply to a message or use with username.""")
await message.answer((
"!srole (@username|id) role(owner,admin,helper,member).\n"
"Reply to a message or use with username."
))
return
if not (new_role in roles["level"].keys()):
if not (new_role in [member.value for member in MemberRoles]):
await message.answer(f"Role {new_role} not exists.")
return
@ -410,11 +444,6 @@ Reply to a message or use with username.""")
await message.answer("❌ You can't set role yourself.")
return
if (roles['level'][new_role] > roles['level'][admin.role]):
await message.answer("Your rank is not high enough to change roles.")
return
database.update_member_data(user.user_id,[Member.role],[new_role])
await message.answer(f"{new_role.capitalize()} role set for [{user.first_name}](tg://user?id={user.user_id}).",
parse_mode="Markdown")
await message.answer(f"{new_role.capitalize()} role set for [{user.first_name}](tg://user?id={user.user_id}).",parse_mode="Markdown")

View File

@ -0,0 +1,55 @@
from load import dp, database, types
from database.models import Member
# TODO: fix it
# import utils
# import config
# vt = utils.VirusTotalAPI(config.vt_api,True)
# @dp.message_handler(content_types=["document"],chat_type=[types.ChatType.SUPERGROUP])
# async def file_handler(message:types.Message):
# file = await bot.get_file(message.document.file_id)
#
# await bot.send_message(
# message.chat.id,
# await vt.scan_file(file.file_path),
# parse_mode="Markdown"
# )
@dp.message_handler(content_types=["new_chat_members"])
async def welcome_message(message:types.Message):
# User
user = message.from_user
exists = database.check_data_exists(Member.user_id,user.id)
if (exists):
await message.answer("Спасибо что вы с нами.")
if not (exists):
database.register_user(user.id,user.first_name,user.username)
# TODO: translate it
await message.answer((
f"Привет,{user.first_name}\n"
"Просим ознакомится с [правилами](https://telegra.ph/Pravila-CHata-Open-Source-05-29)\n"
"Советы на 'хороший тон':\n"
"\t\t1.Формулируй свою мысль в 1-2 предложения\n"
"\t\t1.Не задавай [мета](nometa.xyz) вопросы\n"),
parse_mode="Markdown")
await message.delete()
# @dp.message_handler()
# async def filter_link_shorts(message:types.Message):
# link_shorters = open("txt/link_shorters.txt","r").read().split()
#
# for y in link_shorters:
# for user_message in message.text.lower().split():
# if (y in user_message):await message.delete()
# Joke
@dp.message_handler(content_types=types.ContentType.VOICE)
async def voice_message(message:types.Message):
photo = types.InputFile(path_or_bytesio="media/photo.jpg")
await message.answer_photo(photo)

View File

@ -5,31 +5,6 @@ import config
from load import database
from database.models import Member
@dp.message_handler(content_types=["new_chat_members"])
async def welcome_message(message:types.Message):
# User
user = message.from_user
exists = database.check_data_exists(Member.user_id,user.id)
if (exists):
await message.answer("Спасибо что вы с нами.")
if not (exists):
database.register_user(user.id,user.first_name,user.username)
# TODO: translate it
await message.answer((
f"Привет,{user.first_name}\n"
"Просим ознакомится с [правилами](https://telegra.ph/Pravila-CHata-Open-Source-05-29)\n"
"Советы на 'хороший тон':\n"
"\t\t1.Формулируй свою мысль в 1-2 предложения\n"
"\t\t1.Не задавай [мета](nometa.xyz) вопросы\n"),
parse_mode="Markdown")
await message.delete()
@dp.message_handler(commands=["leave"],chat_type=[types.ChatType.SUPERGROUP])
async def leave_group(message:types.Message):
user = message.from_user
@ -59,6 +34,7 @@ async def start_command_group(message:types.Message):
parse_mode="Markdown"
)
@dp.message_handler(commands=["bio","me"],chat_type=[types.ChatType.SUPERGROUP])
async def get_information(message: types.Message):
user = database.search_single_member(Member.user_id,message.from_user.id)
@ -75,6 +51,7 @@ async def get_information(message: types.Message):
parse_mode="Markdown"
)
@dp.message_handler(commands=["report"],replied=True,chat_type=[types.ChatType.SUPERGROUP])
async def report(message: types.Message):
args = message.text.split()
@ -97,8 +74,4 @@ async def report(message: types.Message):
message.reply_to_message.link("Link message", as_html=False)
))
await bot.send_message(config.telegram_log_chat_id, msg, parse_mode="Markdown")
@dp.message_handler(content_types=["left_chat_member"])
async def event_left_chat(message:types.Message):
await message.delete()
await bot.send_message(config.second_group_id, msg, parse_mode="Markdown")

View File

@ -19,9 +19,9 @@ from keyboards.inline.callback_data import report_callback
@dp.message_handler(commands=["start","help"],chat_type=[types.ChatType.PRIVATE])
async def start_command_private(message:types.Message):
await message.answer((
"Hello,**{message.from_user.first_name}**!\n"
"\t\tMy commands:\n"
"\t\t/help , /start - read this message.")
f"Hello,**{message.from_user.first_name}**!\n"
"My commands:\n"
"\t\t/help /start - read this message.")
,parse_mode="Markdown",reply_markup=menu
)

View File

@ -5,7 +5,7 @@ from aiogram.contrib.fsm_storage.memory import MemoryStorage
import config
import utils
import filters
from database.database import Database
@ -23,7 +23,3 @@ bot = Bot(
)
dp = Dispatcher(bot, storage=storage)
dp.filters_factory.bind(filters.IsAdminFilter)
dp.filters_factory.bind(filters.ReplayMessageFilter)
dp.filters_factory.bind(filters.UserHasRights)

View File

@ -1,4 +1,4 @@
import config
async def notify_started_bot(bot):
await bot.send_message(config.telegram_log_chat_id,"Bot started!")
await bot.send_message(config.second_group_id,"Bot started!")

View File

@ -16,7 +16,6 @@ class TelegramClient:
members = []
async for member in self.client.get_chat_members(chat_id):
try:
username = member.user.username
except AttributeError: