Added /queue, /chats, /clearflood and /close (admin only). Improved general chat experience

This commit is contained in:
nocturn9x 2020-09-06 18:18:12 +02:00
parent 56ec2c9860
commit 52b18b0b93
6 changed files with 116 additions and 24 deletions

View File

@ -21,6 +21,8 @@ FLOOD_PERCENTAGE = 75
ANTIFLOOD_SENSIBILITY = 1
# If you want the user to be notified of being flood-blocked, set this to the desired message, False to disable
FLOOD_NOTICE = f"🤙 **Hey amico**!\n🕐 Rilassati! Sei stato bloccato per {BAN_TIME / 60:.1f} minuti"
FLOOD_CLEARED = "♻️ Tabella antiflood svuotata"
FLOOD_USER_CLEARED = "♻️ Tabella antiflood ripulita per `{user}`"
DELETE_MESSAGES = True # Set this to false if you do not want the messages to be deleted after flood is detected
# Various options and global variables
@ -38,8 +40,8 @@ WORKERS_NUM = 15 # The number of worker threads that pyrogram will spawn at st
BOT_TOKEN = "TOKEN HERE" # Get it with t.me/BotFather
SESSION_NAME = "BotBase" # The name of the Telegram Session that the bot will have, will be visible from Telegram
PLUGINS_ROOT = {"root": f"BotBase/modules"} # Do not change this unless you know what you're doing
API_ID = 1234567 # Get it at https://my.telegram.org/apps
API_HASH = "abcdef123456" # Same as above
API_ID = 123467 # Get it at https://my.telegram.org/apps
API_HASH = "abcdef1234567" # Same as above
# Logging configuration
# To know more about what these options mean, check https://docs.python.org/3/library/logging.html
@ -92,7 +94,7 @@ from .database.query import get_user
# Admin module configuration
# Edit this dict adding the ID:NAME pair of the admin that you want to add. You can add as many admins as you want
ADMINS = {669152898: "Matt-sama :3", 836296867: "αѕσƓAMƐR"}
ADMINS = {123456: "Sample Name"}
MARKED_BUSY = "🎲 Ora sei impegnato, invia nuovamente /busy per resettare questo stato"
UNMARKED_BUSY = "✍ Da ora riceverai nuovamente le richieste di assistenza"
CANNOT_BAN_ADMIN = "❌ L'utente é un amministratore"
@ -110,6 +112,11 @@ NAME = "tg://user?id={}"
BYPASS_FLOOD = True # If False, admins can be flood-blocked too, otherwise the antiflood will ignore them
USER_INFO_UPDATED = "✅ Informazioni aggiornate"
USER_INFO_UNCHANGED = "❌ Non ho rilevato cambiamenti per questo utente"
ADMIN_ACCEPTED_CHAT = "{admin} ha preso in carico la chat con {user}"
USER_LEFT_QUEUE = "⚠️ {user} ha lasciato la coda"
QUEUE_LIST = "🚻 Lista utenti in attesa\n\n{queue}"
CHATS_LIST = "💬 Lista utenti in chat\n\n{chats}"
ADMIN_BUSY = "(Occupato)"
USER_INFO = """** Informazioni**
🆔 **ID**: `{uid}`
@ -165,10 +172,7 @@ def check_user_banned(tg_id: int):
else:
if not res:
return False
if res[-1]:
return True
else:
return False
return res[-1]
def callback_regex(pattern: str):

View File

@ -97,7 +97,7 @@ def set_user(tg_id: int, uname: str):
database.execute(DB_SET_USER, (None, tg_id, uname, time.strftime("%d/%m/%Y %T %p"), 0))
return True
except sqlite3.Error as query_error:
logging.error(f"An error has occurred while executing DB_GET_USERS query: {query_error}")
logging.error(f"An error has occurred while executing DB_SET_USER query: {query_error}")
return query_error

View File

@ -1,7 +1,8 @@
from ..config import ADMINS, USER_INFO, INVALID_SYNTAX, ERROR, NONNUMERIC_ID, USERS_COUNT, \
NO_PARAMETERS, ID_MISSING, GLOBAL_MESSAGE_STATS, NAME, WHISPER_FROM, USER_INFO_UPDATED, USER_INFO_UNCHANGED, \
USER_BANNED, USER_UNBANNED, CANNOT_BAN_ADMIN, USER_ALREADY_BANNED, USER_NOT_BANNED, YOU_ARE_BANNED, YOU_ARE_UNBANNED, \
MARKED_BUSY, UNMARKED_BUSY, CACHE, YES, NO, NAME_MISSING, bot, WHISPER_SUCCESSFUL
MARKED_BUSY, UNMARKED_BUSY, CACHE, YES, NO, NAME_MISSING, bot, WHISPER_SUCCESSFUL, LEAVE_CURRENT_CHAT, \
QUEUE_LIST, CHATS_LIST, ADMIN_BUSY
from pyrogram import Client, Filters
from ..database.query import get_user, get_users, update_name, ban_user, unban_user, get_user_by_name
from .antiflood import BANNED_USERS
@ -226,10 +227,39 @@ def busy(client, message):
if len(message.command) > 1:
wrapper.send_message(message.chat.id, f"{NO_PARAMETERS.format(command='/busy')}")
else:
if CACHE[message.from_user.id][0] == "none":
if CACHE[message.from_user.id][0] == "IN_CHAT" and CACHE[message.from_user.id][1] != 1234567:
wrapper.send_message(message.from_user.id, LEAVE_CURRENT_CHAT)
elif CACHE[message.from_user.id][0] == "none":
wrapper.send_message(message.chat.id, MARKED_BUSY)
CACHE[message.from_user.id] = ["IN_CHAT", 1234567]
else:
if message.from_user.id in CACHE:
del CACHE[message.from_user.id]
wrapper.send_message(message.chat.id, UNMARKED_BUSY)
@Client.on_message(Filters.command("chats") & ADMINS_FILTER & Filters.private & ~BANNED_USERS & ~Filters.edited)
def chats(client, message):
logging.warning(f"{ADMINS[message.from_user.id]} [{message.from_user.id}] sent /chats")
if len(message.command) > 1:
wrapper.send_message(message.chat.id, f"{NO_PARAMETERS.format(command='/chats')}")
else:
text = ""
for user in CACHE:
if CACHE[user][0] == "IN_CHAT" and user not in ADMINS:
admin_id = CACHE[user][1]
admin_name = ADMINS[admin_id]
text += f"- 👤 [User]({NAME.format(user)}) -> 👨‍💻 [{admin_name}]({NAME.format(admin_id)})\n"
wrapper.send_message(message.chat.id, CHATS_LIST.format(chats=text))
@Client.on_message(Filters.command("queue") & ADMINS_FILTER & Filters.private & ~BANNED_USERS & ~Filters.edited)
def queue(client, message):
logging.warning(f"{ADMINS[message.from_user.id]} [{message.from_user.id}] sent /queue")
if len(message.command) > 1:
wrapper.send_message(message.chat.id, f"{NO_PARAMETERS.format(command='/queue')}")
else:
text = ""
for user in CACHE:
if CACHE[user][0] == "AWAITING_ADMIN":
text += f"- 👤 [User]({NAME.format(user)})\n"
wrapper.send_message(message.chat.id, QUEUE_LIST.format(queue=text))

View File

@ -1,24 +1,27 @@
from pyrogram import Client, Filters
from ..config import MAX_UPDATE_THRESHOLD, ANTIFLOOD_SENSIBILITY, BAN_TIME, ADMINS, BYPASS_FLOOD, FLOOD_NOTICE, \
FLOOD_PERCENTAGE, CACHE, PRIVATE_ONLY, DELETE_MESSAGES, bot, user_banned
FLOOD_PERCENTAGE, CACHE, PRIVATE_ONLY, DELETE_MESSAGES, bot, user_banned, FLOOD_CLEARED, FLOOD_USER_CLEARED, \
ERROR, NONNUMERIC_ID, check_user_banned
from collections import defaultdict
import logging
import time
from ..methods import MethodWrapper
from ..config import check_user_banned
# Some variables for runtime configuration
MESSAGES = defaultdict(list) # Internal variable for the antiflood module
BANNED_USERS = Filters.user() # Filters where the antiflood will put banned users
BYPASS_USERS = Filters.user(list(ADMINS.keys())) if BYPASS_FLOOD else Filters.user()
ADMINS = Filters.user(list(ADMINS.keys()))
FILTER = Filters.private if PRIVATE_ONLY else ~Filters.user()
wrapper = MethodWrapper(bot)
def is_flood(updates: list):
"""Calculates if a sequence of
updates corresponds to a flood"""
"""
Calculates if a sequence of
updates corresponds to a flood
"""
genexpr = [i <= ANTIFLOOD_SENSIBILITY for i in
((updates[i + 1] - timestamp) if i < (MAX_UPDATE_THRESHOLD - 1) else (timestamp - updates[i - 1]) for
@ -40,7 +43,7 @@ def anti_flood(client, update):
logging.warning(f"{user_id} has waited at least {BAN_TIME} seconds in {chat} and can now text again")
BANNED_USERS.remove(user_id)
del MESSAGES[user_id]
elif len(MESSAGES[user_id]) >= MAX_UPDATE_THRESHOLD:
elif len(MESSAGES[user_id]) >= MAX_UPDATE_THRESHOLD - 1: # -1 to avoid acting on the next update
MESSAGES[user_id].append({chat: (date, message_id)})
logging.info(f"MAX_UPDATE_THRESHOLD ({MAX_UPDATE_THRESHOLD}) Reached for {user_id}")
user_data = MESSAGES.pop(user_id)
@ -61,3 +64,21 @@ def anti_flood(client, update):
del MESSAGES[user_id]
else:
MESSAGES[user_id].append({chat: (date, message_id)})
@Client.on_message(FILTER & ADMINS & ~Filters.edited & Filters.command("clearflood"))
def clear_flood(_, message):
if len(message.command) == 1:
global MESSAGES # Ew...
MESSAGES = defaultdict(list)
for user in BANNED_USERS.copy():
BANNED_USERS.remove(user)
wrapper.send_message(message.chat.id, FLOOD_CLEARED)
else:
for user in message.command[1:]:
if not user.isdigit():
wrapper.send_message(message.chat.id, f"{ERROR}: {NONNUMERIC_ID}")
return
BANNED_USERS.discard(int(user))
MESSAGES.pop(int(user), None)
wrapper.send_message(message.chat.id, FLOOD_USER_CLEARED.format(user=", ".join((f"{usr}" for usr in message.command[1:]))))

View File

@ -2,7 +2,8 @@ from pyrogram import Client, Filters, InlineKeyboardMarkup, InlineKeyboardButton
from ..config import CACHE, ADMINS, ADMINS_LIST_UPDATE_DELAY, callback_regex, admin_is_chatting, \
user_is_chatting, LIVE_CHAT_STATUSES, STATUS_BUSY, STATUS_FREE, SUPPORT_REQUEST_SENT, SUPPORT_NOTIFICATION, \
ADMIN_JOINS_CHAT, USER_CLOSES_CHAT, JOIN_CHAT_BUTTON, USER_INFO, USER_LEAVES_CHAT, ADMIN_MESSAGE, USER_MESSAGE, \
TOO_FAST, CHAT_BUSY, LEAVE_CURRENT_CHAT, USER_JOINS_CHAT, NAME, CANNOT_REQUEST_SUPPORT, YES, NO, user_banned, CLOSE_CHAT_BUTTON, BACK_BUTTON, UPDATE_BUTTON, bot
TOO_FAST, CHAT_BUSY, LEAVE_CURRENT_CHAT, USER_JOINS_CHAT, NAME, CANNOT_REQUEST_SUPPORT, YES, NO, user_banned, CLOSE_CHAT_BUTTON, BACK_BUTTON, UPDATE_BUTTON, bot, \
ADMIN_ACCEPTED_CHAT
import time
from ..database.query import get_user
from .antiflood import BANNED_USERS
@ -20,7 +21,7 @@ BUTTONS = InlineKeyboardMarkup(
wrapper = MethodWrapper(bot)
@Client.on_callback_query(Filters.callback_data("sos") & ~BANNED_USERS & ~user_banned())
@Client.on_callback_query(Filters.regex("sos") & ~BANNED_USERS & ~user_banned())
def begin_chat(client, query):
cb_wrapper = MethodWrapper(query)
if query.from_user.id in ADMINS:
@ -53,7 +54,7 @@ def begin_chat(client, query):
CACHE[query.from_user.id][-1].append((msg.chat.id, msg.message_id))
@Client.on_callback_query(Filters.callback_data("update_admins_list") & ~BANNED_USERS & ~user_banned())
@Client.on_callback_query(Filters.regex("update_admins_list") & ~BANNED_USERS & ~user_banned())
def update_admins_list(_, query):
cb_wrapper = MethodWrapper(query)
if CACHE[query.from_user.id][0] == "AWAITING_ADMIN":
@ -135,7 +136,26 @@ def close_chat(_, query):
def forward_from_admin(client, message):
if message.text:
logging.warning(f"Admin {ADMINS[message.from_user.id]} [{message.from_user.id}] says to {CACHE[message.from_user.id][1]}: {message.text.html}")
wrapper.send_message(CACHE[message.from_user.id][1],
if message.text == "/close":
admin_id = message.from_user.id
user_id = CACHE[admin_id][1]
data = CACHE[user_id][-1]
if isinstance(data, list):
data.append((message.from_user.id, message.message_id))
for chatid, message_ids in data:
wrapper.delete_messages(chatid, message_ids)
status = CACHE[message.from_user.id][0]
if status == "IN_CHAT":
wrapper.send_message(message.from_user.id, USER_LEAVES_CHAT)
admin_id, admin_name = message.from_user.id, ADMINS[message.from_user.id]
logging.warning(f"{ADMINS[admin_id]} [{admin_id}] has terminated the chat with user {CACHE[admin_id][1]}")
if CACHE[user_id][0] == "IN_CHAT":
del CACHE[user_id]
wrapper.send_message(user_id,
USER_CLOSES_CHAT.format(user_id=NAME.format(admin_id), user_name=admin_name))
del CACHE[message.from_user.id]
else:
wrapper.send_message(CACHE[message.from_user.id][1],
ADMIN_MESSAGE.format(user_name=ADMINS[message.from_user.id],
user_id=NAME.format(message.from_user.id),
message=message.text.html))
@ -205,6 +225,15 @@ def join_chat(_, query):
cb_wrapper = MethodWrapper(query)
if CACHE[query.from_user.id][0] != "IN_CHAT":
user_id = int(query.data.split("_")[1])
user = wrapper.get_users(user_id)
if isinstance(user, Exception):
user_name = "Anonymous"
elif user.first_name:
user_name = user.first_name
elif user.username:
user_name = user.username
else:
user_name = "Anonymous"
if CACHE[user_id][0] != "AWAITING_ADMIN":
cb_wrapper.answer(CHAT_BUSY)
else:
@ -215,8 +244,12 @@ def join_chat(_, query):
message = wrapper.send_message(query.from_user.id, USER_JOINS_CHAT, reply_markup=buttons)
admin_joins = wrapper.send_message(user_id, ADMIN_JOINS_CHAT.format(admin_name=admin_name, admin_id=NAME.format(admin_id)),
reply_markup=buttons)
logging.warning(f"{admin_name} [{admin_id}] has joined a chat with user {user_name} [{user_id}]")
for chatid, message_ids in CACHE[user_id][-1]:
wrapper.delete_messages(chatid, message_ids)
for admin in ADMINS:
if CACHE[admin] != "IN_CHAT" and admin != admin_id:
wrapper.send_message(admin, ADMIN_ACCEPTED_CHAT.format(admin=f"[{admin_name}]({NAME.format(admin)})", user=f"[{user_name}]({NAME.format(user_id)})"))
CACHE[user_id][-1].append((message.chat.id, message.message_id))
CACHE[user_id][-1].append((admin_joins.chat.id, admin_joins.message_id))
else:

View File

@ -1,6 +1,6 @@
from pyrogram import Client, Filters, InlineKeyboardButton, InlineKeyboardMarkup
from .antiflood import BANNED_USERS
from ..config import GREET, BUTTONS, CREDITS, CACHE, bot, VERSION, RELEASE_DATE, user_banned, BACK_BUTTON
from ..config import GREET, BUTTONS, CREDITS, CACHE, bot, VERSION, RELEASE_DATE, user_banned, BACK_BUTTON, USER_LEFT_QUEUE, ADMINS, NAME
from ..database.query import get_users, set_user
import logging
import itertools
@ -33,14 +33,14 @@ def start_handler(client, message):
)
@Client.on_callback_query(Filters.callback_data("info") & ~BANNED_USERS)
@Client.on_callback_query(Filters.regex("info") & ~BANNED_USERS)
def bot_info(_, query):
cb_wrapper = MethodWrapper(query)
buttons = InlineKeyboardMarkup([[InlineKeyboardButton(BACK_BUTTON, "back_start")]])
cb_wrapper.edit_message_text(CREDITS.format(VERSION=VERSION, RELEASE_DATE=RELEASE_DATE), reply_markup=buttons)
@Client.on_callback_query(Filters.callback_data("back_start") & ~BANNED_USERS)
@Client.on_callback_query(Filters.regex("back_start") & ~BANNED_USERS)
def back_start(_, query):
cb_wrapper = MethodWrapper(query)
if query.from_user.first_name:
@ -52,8 +52,12 @@ def back_start(_, query):
if CACHE[query.from_user.id][0] == "AWAITING_ADMIN":
data = CACHE[query.from_user.id][-1]
if isinstance(data, list):
for chatid, message_ids in data[:-2]:
for chatid, message_ids in data:
wrapper.delete_messages(chatid, message_ids)
cb_wrapper.edit_message_text(GREET.format(mention=f"[{name}](tg://user?id={query.from_user.id})", id=query.from_user.id,
for admin in ADMINS:
wrapper.send_message(admin, USER_LEFT_QUEUE.format(user=f"[{name}]({NAME.format(query.from_user.id)})"))
wrapper.send_message(query.from_user.id, GREET.format(mention=f"[{name}](tg://user?id={query.from_user.id})", id=query.from_user.id,
username=query.from_user.username),
reply_markup=BUTTONS)
del CACHE[query.from_user.id]