diff --git a/Database/mongodb/afk_db.py b/Database/mongodb/afk_db.py new file mode 100644 index 0000000000000000000000000000000000000000..f6b844b6d29af274b04d66c8f65d13271d1bcfe0 --- /dev/null +++ b/Database/mongodb/afk_db.py @@ -0,0 +1,53 @@ +from Database.mongodb.db import dbname + +usersdb = dbname.users +cleandb = dbname.cleanmode +cleanmode = {} + + +async def is_cleanmode_on(chat_id: int) -> bool: + mode = cleanmode.get(chat_id) + if not mode: + user = await cleandb.find_one({"chat_id": chat_id}) + if not user: + cleanmode[chat_id] = True + return True + cleanmode[chat_id] = False + return False + return mode + + +async def cleanmode_on(chat_id: int): + cleanmode[chat_id] = True + user = await cleandb.find_one({"chat_id": chat_id}) + if user: + return await cleandb.delete_one({"chat_id": chat_id}) + + +async def cleanmode_off(chat_id: int): + cleanmode[chat_id] = False + user = await cleandb.find_one({"chat_id": chat_id}) + if not user: + return await cleandb.insert_one({"chat_id": chat_id}) + + +async def is_afk(user_id: int) -> bool: + user = await usersdb.find_one({"user_id": user_id}) + return (True, user["reason"]) if user else (False, {}) + + +async def add_afk(user_id: int, mode): + await usersdb.update_one( + {"user_id": user_id}, {"$set": {"reason": mode}}, upsert=True + ) + + +async def remove_afk(user_id: int): + user = await usersdb.find_one({"user_id": user_id}) + if user: + return await usersdb.delete_one({"user_id": user_id}) + + +async def get_afk_users() -> list: + users = usersdb.find({"user_id": {"$gt": 0}}) + return list(await users.to_list(length=1000000000)) if users else [] diff --git a/Database/mongodb/blacklistdb.py b/Database/mongodb/blacklistdb.py new file mode 100644 index 0000000000000000000000000000000000000000..f6a58beb2c3290b65d8d54000b4c2d2e0549ae11 --- /dev/null +++ b/Database/mongodb/blacklistdb.py @@ -0,0 +1,51 @@ +import codecs +import pickle +from typing import List + +from Database.mongodb.db import dbname + +blacklist_filtersdb = dbname.blacklistFilters + + +def obj_to_str(obj): + if not obj: + return False + string = codecs.encode(pickle.dumps(obj), "base64").decode() + return string + + +def str_to_obj(string: str): + obj = pickle.loads(codecs.decode(string.encode(), "base64")) + return obj + + +async def get_blacklisted_words(chat_id: int) -> List[str]: + _filters = await blacklist_filtersdb.find_one({"chat_id": chat_id}) + if not _filters: + return [] + return _filters["filters"] + + +async def save_blacklist_filter(chat_id: int, word: str): + word = word.lower().strip() + _filters = await get_blacklisted_words(chat_id) + _filters.append(word) + await blacklist_filtersdb.update_one( + {"chat_id": chat_id}, + {"$set": {"filters": _filters}}, + upsert=True, + ) + + +async def delete_blacklist_filter(chat_id: int, word: str) -> bool: + filtersd = await get_blacklisted_words(chat_id) + word = word.lower().strip() + if word in filtersd: + filtersd.remove(word) + await blacklist_filtersdb.update_one( + {"chat_id": chat_id}, + {"$set": {"filters": filtersd}}, + upsert=True, + ) + return True + return False diff --git a/Database/mongodb/db.py b/Database/mongodb/db.py new file mode 100644 index 0000000000000000000000000000000000000000..7d133f1573c7ee2623a5f611cc402725aab61407 --- /dev/null +++ b/Database/mongodb/db.py @@ -0,0 +1,6 @@ +from motor.motor_asyncio import AsyncIOMotorClient as MongoClient + +from Mikobot import DB_NAME, MONGO_DB_URI + +mongo = MongoClient(MONGO_DB_URI) +dbname = mongo[DB_NAME] diff --git a/Database/mongodb/fsub_db.py b/Database/mongodb/fsub_db.py new file mode 100644 index 0000000000000000000000000000000000000000..4443bab73ecda4f4b864adb0e455eb4ca6db810c --- /dev/null +++ b/Database/mongodb/fsub_db.py @@ -0,0 +1,18 @@ +from Infamous.temp import dbname + +fsub = dbname.force_sub + + +def fs_settings(chat_id: int): + _x = fsub.find_one({"chat_id": chat_id}) + if _x: + return _x + return None + + +def add_channel(chat_id: int, channel): + fsub.update_one({"chat_id": chat_id}, {"$set": {"channel": channel}}, upsert=True) + + +def disapprove(chat_id: int): + fsub.delete_one({"chat_id": chat_id}) diff --git a/Database/mongodb/karma_mongo.py b/Database/mongodb/karma_mongo.py new file mode 100644 index 0000000000000000000000000000000000000000..cdd86ee64758acd2e422027c6c8de07729802293 --- /dev/null +++ b/Database/mongodb/karma_mongo.py @@ -0,0 +1,122 @@ +from typing import Dict, Union + +from pymongo import MongoClient + +from Mikobot import DB_NAME, MONGO_DB_URI + +client = MongoClient(MONGO_DB_URI) +db = client[DB_NAME] + +coupledb = db.couple +karmadb = db.karma + + +async def _get_lovers(chat_id: int): + lovers = coupledb.find_one({"chat_id": chat_id}) + if lovers: + lovers = lovers["couple"] + else: + lovers = {} + return lovers + + +async def get_couple(chat_id: int, date: str): + lovers = await _get_lovers(chat_id) + if date in lovers: + return lovers[date] + else: + return False + + +async def save_couple(chat_id: int, date: str, couple: dict): + lovers = await _get_lovers(chat_id) + lovers[date] = couple + coupledb.update_one({"chat_id": chat_id}, {"$set": {"couple": lovers}}, upsert=True) + + +async def get_karmas_count() -> dict: + chats = karmadb.find({"chat_id": {"$lt": 0}}) + if not chats: + return {} + chats_count = 0 + karmas_count = 0 + for chat in await chats.to_list(length=1000000): + for i in chat["karma"]: + karma_ = chat["karma"][i]["karma"] + if karma_ > 0: + karmas_count += karma_ + chats_count += 1 + return {"chats_count": chats_count, "karmas_count": karmas_count} + + +async def user_global_karma(user_id) -> int: + chats = karmadb.find({"chat_id": {"$lt": 0}}) + if not chats: + return 0 + total_karma = 0 + for chat in await chats.to_list(length=1000000): + karma = await get_karma(chat["chat_id"], await int_to_alpha(user_id)) + if karma and (int(karma["karma"]) > 0): + total_karma += int(karma["karma"]) + return total_karma + + +async def get_karmas(chat_id: int) -> Dict[str, int]: + karma = karmadb.find_one({"chat_id": chat_id}) + if not karma: + return {} + return karma["karma"] + + +async def get_karma(chat_id: int, name: str) -> Union[bool, dict]: + name = name.lower().strip() + karmas = await get_karmas(chat_id) + if name in karmas: + return karmas[name] + + +async def update_karma(chat_id: int, name: str, karma: dict): + name = name.lower().strip() + karmas = await get_karmas(chat_id) + karmas[name] = karma + karmadb.update_one({"chat_id": chat_id}, {"$set": {"karma": karmas}}, upsert=True) + + +async def is_karma_on(chat_id: int) -> bool: + chat = karmadb.find_one({"chat_id_toggle": chat_id}) + if not chat: + return True + return False + + +async def karma_on(chat_id: int): + is_karma = await is_karma_on(chat_id) + if is_karma: + return + return karmadb.delete_one({"chat_id_toggle": chat_id}) + + +async def karma_off(chat_id: int): + is_karma = await is_karma_on(chat_id) + if not is_karma: + return + return karmadb.insert_one({"chat_id_toggle": chat_id}) + + +async def int_to_alpha(user_id: int) -> str: + alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] + text = "" + user_id = str(user_id) + for i in user_id: + text += alphabet[int(i)] + return text + + +async def alpha_to_int(user_id_alphabet: str) -> int: + alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] + user_id = "" + for i in user_id_alphabet: + index = alphabet.index(i) + user_id += str(index) + user_id = int(user_id) + return user_id diff --git a/Database/mongodb/locale_db.py b/Database/mongodb/locale_db.py new file mode 100644 index 0000000000000000000000000000000000000000..5f830e9eb2c341c781f78cd0b29f96ddac7a2b11 --- /dev/null +++ b/Database/mongodb/locale_db.py @@ -0,0 +1,22 @@ +from typing import Iterable + +from pyrogram.enums import ChatType + +from Database.mongodb.db import dbname + +localesdb = dbname.locale # DB for localization + +group_types: Iterable[ChatType] = (ChatType.GROUP, ChatType.SUPERGROUP) + + +async def set_db_lang(chat_id: int, chat_type: str, lang_code: str): + await localesdb.update_one( + {"chat_id": chat_id}, + {"$set": {"lang": lang_code, "chat_type": chat_type.value}}, + upsert=True, + ) + + +async def get_db_lang(chat_id: int) -> str: + ul = await localesdb.find_one({"chat_id": chat_id}) + return ul["lang"] if ul else {} diff --git a/Database/mongodb/mongodb.py b/Database/mongodb/mongodb.py new file mode 100644 index 0000000000000000000000000000000000000000..25131657a6ec4fd6a16da112e1c5d5c6aacf5a8c --- /dev/null +++ b/Database/mongodb/mongodb.py @@ -0,0 +1,75 @@ +from sys import exit as exiter + +from pymongo import MongoClient +from pymongo.errors import PyMongoError + +from Mikobot import DB_NAME, LOGGER, MONGO_DB_URI + +try: + Mikobot_db_client = MongoClient(MONGO_DB_URI) +except PyMongoError as f: + LOGGER.error(f"Error in Mongodb: {f}") + exiter(1) +Mikobot_main_db = Mikobot_db_client[DB_NAME] + + +class MongoDB: + """Class for interacting with Bot database.""" + + def __init__(self, collection) -> None: + self.collection = Mikobot_main_db[collection] + + # Insert one entry into collection + def insert_one(self, document): + result = self.collection.insert_one(document) + return repr(result.inserted_id) + + # Find one entry from collection + def find_one(self, query): + result = self.collection.find_one(query) + if result: + return result + return False + + # Find entries from collection + def find_all(self, query=None): + if query is None: + query = {} + return list(self.collection.find(query)) + + # Count entries from collection + def count(self, query=None): + if query is None: + query = {} + return self.collection.count_documents(query) + + # Delete entry/entries from collection + def delete_one(self, query): + self.collection.delete_many(query) + return self.collection.count_documents({}) + + # Replace one entry in collection + def replace(self, query, new_data): + old = self.collection.find_one(query) + _id = old["_id"] + self.collection.replace_one({"_id": _id}, new_data) + new = self.collection.find_one({"_id": _id}) + return old, new + + # Update one entry from collection + def update(self, query, update): + result = self.collection.update_one(query, {"$set": update}) + new_document = self.collection.find_one(query) + return result.modified_count, new_document + + @staticmethod + def close(): + return Mikobot_db_client.close() + + +def __connect_first(): + _ = MongoDB("test") + LOGGER.info("Initialized Mongo Database!\n") + + +__connect_first() diff --git a/Database/mongodb/sangmata_db.py b/Database/mongodb/sangmata_db.py new file mode 100644 index 0000000000000000000000000000000000000000..10229c335d1cb887adb0cc338ecac679cd44a162 --- /dev/null +++ b/Database/mongodb/sangmata_db.py @@ -0,0 +1,42 @@ +from Database.mongodb.db import dbname + +matadb = dbname["sangmata"] + + +# Get Data User +async def cek_userdata(user_id: int) -> bool: + user = await matadb.find_one({"user_id": user_id}) + return bool(user) + + +async def get_userdata(user_id: int) -> bool: + user = await matadb.find_one({"user_id": user_id}) + return user["username"], user["first_name"], user["last_name"] + + +async def add_userdata(user_id: int, username, first_name, last_name): + await matadb.update_one( + {"user_id": user_id}, + { + "$set": { + "username": username, + "first_name": first_name, + "last_name": last_name, + } + }, + upsert=True, + ) + + +# Enable Mata MissKaty in Selected Chat +async def is_sangmata_on(chat_id: int) -> bool: + chat = await matadb.find_one({"chat_id_toggle": chat_id}) + return bool(chat) + + +async def sangmata_on(chat_id: int) -> bool: + await matadb.insert_one({"chat_id_toggle": chat_id}) + + +async def sangmata_off(chat_id: int): + await matadb.delete_one({"chat_id_toggle": chat_id}) diff --git a/Database/mongodb/toggle_mongo.py b/Database/mongodb/toggle_mongo.py new file mode 100644 index 0000000000000000000000000000000000000000..5fb8cafde7a4947b13d2c34c1ebb1b10e0ccb93e --- /dev/null +++ b/Database/mongodb/toggle_mongo.py @@ -0,0 +1,50 @@ +from Database.mongodb.db import * + +dwelcomedb = dbname.dwelcome +nsfwdb = dbname.nsfw +nekomodedb = dbname.nekomode + + +async def is_dwelcome_on(chat_id: int) -> bool: + chat = await dwelcomedb.find_one({"chat_id_toggle": chat_id}) + return not bool(chat) + + +async def dwelcome_on(chat_id: int): + await dwelcomedb.delete_one({"chat_id_toggle": chat_id}) + + +async def dwelcome_off(chat_id: int): + await dwelcomedb.insert_one({"chat_id_toggle": chat_id}) + + +async def is_nsfw_on(chat_id: int) -> bool: + chat = await nsfwdb.find_one({"chat_id": chat_id}) + return chat + + +async def nsfw_on(chat_id: int): + is_nsfw = await is_nsfw_on(chat_id) + if is_nsfw: + return + return await nsfwdb.insert_one({"chat_id": chat_id}) + + +async def nsfw_off(chat_id: int): + is_nsfw = await is_nsfw_on(chat_id) + if not is_nsfw: + return + return await nsfwdb.delete_one({"chat_id": chat_id}) + + +async def is_nekomode_on(chat_id: int) -> bool: + chat = await nekomodedb.find_one({"chat_id_toggle": chat_id}) + return not bool(chat) + + +async def nekomode_on(chat_id: int): + await nekomodedb.delete_one({"chat_id_toggle": chat_id}) + + +async def nekomode_off(chat_id: int): + await nekomodedb.insert_one({"chat_id_toggle": chat_id}) diff --git a/Database/mongodb/users_chats_db.py b/Database/mongodb/users_chats_db.py new file mode 100644 index 0000000000000000000000000000000000000000..987ce662c9e81cca490fc3f9ac6690b63ad5f8c7 --- /dev/null +++ b/Database/mongodb/users_chats_db.py @@ -0,0 +1,111 @@ +from async_pymongo import AsyncClient + +from Mikobot import DB_NAME, MONGO_DB_URI + + +class UsersData: + def __init__(self, uri, database_name): + self._client = AsyncClient(uri) + self.db = self._client[database_name] + self.col = self.db["userlist"] + self.grp = self.db["groups"] + + @staticmethod + def new_user(id, name): + return dict( + id=id, + name=name, + ban_status=dict( + is_banned=False, + ban_reason="", + ), + ) + + @staticmethod + def new_group(id, title): + return dict( + id=id, + title=title, + chat_status=dict( + is_disabled=False, + reason="", + ), + ) + + async def add_user(self, id, name): + user = self.new_user(id, name) + await self.col.insert_one(user) + + async def is_user_exist(self, id): + user = await self.col.find_one({"id": int(id)}) + return bool(user) + + async def total_users_count(self): + return await self.col.count_documents({}) + + async def remove_ban(self, id): + return await self.col.delete_one({"_id": id}) + + async def ban_user(self, user_id, ban_reason="No Reason"): + return await self.col.insert_one({"_id": user_id, "reason": ban_reason}) + + async def get_ban_status(self, id): + user = await self.col.find_one({"_id": int(id)}) + if user: + return True, user + return False, None + + async def get_all_users(self): + return self.col.find({}) + + async def delete_user(self, user_id): + await self.col.delete_many({"id": int(user_id)}) + + async def is_chat_exist(self, id): + user = await self.grp.find_one({"id": int(id)}) + return bool(user) + + async def get_banned(self): + users = self.col.find({"ban_status.is_banned": True}) + chats = self.grp.find({"chat_status.is_disabled": True}) + b_chats = [chat["id"] async for chat in chats] + b_users = [user["id"] async for user in users] + return b_users, b_chats + + async def add_chat(self, chat, title): + chat = self.new_group(chat, title) + await self.grp.insert_one(chat) + + async def get_chat(self, chat): + chat = await self.grp.find_one({"id": int(chat)}) + return chat.get("chat_status") if chat else False + + async def re_enable_chat(self, id): + chat_status = dict( + is_disabled=False, + reason="", + ) + await self.grp.update_one( + {"id": int(id)}, {"$set": {"chat_status": chat_status}} + ) + + async def disable_chat(self, chat, reason="No Reason"): + chat_status = dict( + is_disabled=True, + reason=reason, + ) + await self.grp.update_one( + {"id": int(chat)}, {"$set": {"chat_status": chat_status}} + ) + + async def total_chat_count(self): + return await self.grp.count_documents({}) + + async def get_all_chats(self): + return self.grp.find({}) + + async def get_db_size(self): + return (await self.db.command("dbstats"))["dataSize"] + + +db = UsersData(MONGO_DB_URI, DB_NAME) diff --git a/Database/mongodb/users_db.py b/Database/mongodb/users_db.py new file mode 100644 index 0000000000000000000000000000000000000000..9e07fa175b256165b03ae76e164f4d37a2a1512c --- /dev/null +++ b/Database/mongodb/users_db.py @@ -0,0 +1,102 @@ +from threading import RLock +from time import time + +from Database.mongodb.mongodb import MongoDB +from Mikobot import LOGGER + +INSERTION_LOCK = RLock() + + +class Users(MongoDB): + """Class to manage users for bot.""" + + db_name = "users" + + def __init__(self, user_id: int) -> None: + super().__init__(self.db_name) + self.user_id = user_id + self.user_info = self.__ensure_in_db() + + def update_user(self, name: str, username: str = None): + with INSERTION_LOCK: + if name != self.user_info["name"] or username != self.user_info["username"]: + return self.update( + {"_id": self.user_id}, + {"username": username, "name": name}, + ) + return True + + def delete_user(self): + with INSERTION_LOCK: + return self.delete_one({"_id": self.user_id}) + + @staticmethod + def count_users(): + with INSERTION_LOCK: + collection = MongoDB(Users.db_name) + return collection.count() + + def get_my_info(self): + with INSERTION_LOCK: + return self.user_info + + @staticmethod + def list_users(): + with INSERTION_LOCK: + collection = MongoDB(Users.db_name) + return collection.find_all() + + @staticmethod + def get_user_info(user_id: int or str): + with INSERTION_LOCK: + collection = MongoDB(Users.db_name) + if isinstance(user_id, int): + curr = collection.find_one({"_id": user_id}) + elif isinstance(user_id, str): + # user_id[1:] because we don't want the '@' in the username + # search! + curr = collection.find_one({"username": user_id[1:]}) + else: + curr = None + + if curr: + return curr + + return {} + + def __ensure_in_db(self): + chat_data = self.find_one({"_id": self.user_id}) + if not chat_data: + new_data = {"_id": self.user_id, "username": "", "name": "unknown_till_now"} + self.insert_one(new_data) + LOGGER.info(f"Initialized User Document for {self.user_id}") + return new_data + return chat_data + + @staticmethod + def load_from_db(): + with INSERTION_LOCK: + collection = MongoDB(Users.db_name) + return collection.find_all() + + @staticmethod + def repair_db(collection): + all_data = collection.find_all() + keys = {"username": "", "name": "unknown_till_now"} + for data in all_data: + for key, val in keys.items(): + try: + _ = data[key] + except KeyError: + LOGGER.warning( + f"Repairing Users Database - setting '{key}:{val}' for {data['_id']}", + ) + collection.update({"_id": data["_id"]}, {key: val}) + + +def __pre_req_users(): + start = time() + LOGGER.info("Starting Users Database Repair...") + collection = MongoDB(Users.db_name) + Users.repair_db(collection) + LOGGER.info(f"Done in {round((time() - start), 3)}s!") diff --git a/Database/mongodb/whispers.py b/Database/mongodb/whispers.py new file mode 100644 index 0000000000000000000000000000000000000000..445f6d9ea6a2c4c07ca0f1126151f27c8a728f30 --- /dev/null +++ b/Database/mongodb/whispers.py @@ -0,0 +1,19 @@ +from Database.mongodb.db import dbname + +collection = dbname["whisper"] + + +class Whispers: + @staticmethod + async def add_whisper(WhisperId, WhisperData): + whisper = {"WhisperId": WhisperId, "whisperData": WhisperData} + await collection.insert_one(whisper) + + @staticmethod + async def del_whisper(WhisperId): + await collection.delete_one({"WhisperId": WhisperId}) + + @staticmethod + async def get_whisper(WhisperId): + whisper = await collection.find_one({"WhisperId": WhisperId}) + return whisper["whisperData"] if whisper else None diff --git a/Database/sql/__init__.py b/Database/sql/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8dfa486555d9259c4de27540a0f409e4a04a5b65 --- /dev/null +++ b/Database/sql/__init__.py @@ -0,0 +1,27 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import scoped_session, sessionmaker + +from Mikobot import DB_URI +from Mikobot import LOGGER as log + +if DB_URI and DB_URI.startswith("postgres://"): + DB_URI = DB_URI.replace("postgres://", "postgresql://", 1) + + +def start() -> scoped_session: + engine = create_engine(DB_URI, client_encoding="utf8") + log.info("[PostgreSQL] Connecting to database......") + BASE.metadata.bind = engine + BASE.metadata.create_all(engine) + return scoped_session(sessionmaker(bind=engine, autoflush=False)) + + +BASE = declarative_base() +try: + SESSION = start() +except Exception as e: + log.exception(f"[PostgreSQL] Failed to connect due to {e}") + exit() + +log.info("[PostgreSQL] Connection successful, session started.") diff --git a/Database/sql/afk_sql.py b/Database/sql/afk_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..b925bb4603b7127f59fac4f1aa9106e55b793841 --- /dev/null +++ b/Database/sql/afk_sql.py @@ -0,0 +1,99 @@ +import threading +from datetime import datetime + +from sqlalchemy import BigInteger, Boolean, Column, DateTime, UnicodeText + +from Database.sql import BASE, SESSION + + +class AFK(BASE): + __tablename__ = "afk_user" + + user_id = Column(BigInteger, primary_key=True) + is_afk = Column(Boolean) + reason = Column(UnicodeText) + time = Column(DateTime) + + def __init__(self, user_id: int, reason: str = "", is_afk: bool = True): + self.user_id = user_id + self.reason = reason + self.is_afk = is_afk + self.time = datetime.now() + + def __repr__(self): + return "afk_status for {}".format(self.user_id) + + +AFK.__table__.create(checkfirst=True) +INSERTION_LOCK = threading.RLock() + +AFK_USERS = {} + + +def is_afk(user_id): + return user_id in AFK_USERS + + +def check_afk_status(user_id): + try: + return SESSION.query(AFK).get(user_id) + finally: + SESSION.close() + + +def set_afk(user_id, reason=""): + with INSERTION_LOCK: + curr = SESSION.query(AFK).get(user_id) + if not curr: + curr = AFK(user_id, reason, True) + else: + curr.is_afk = True + + AFK_USERS[user_id] = {"reason": reason, "time": curr.time} + + SESSION.add(curr) + SESSION.commit() + + +def rm_afk(user_id): + with INSERTION_LOCK: + curr = SESSION.query(AFK).get(user_id) + if curr: + if user_id in AFK_USERS: # sanity check + del AFK_USERS[user_id] + + SESSION.delete(curr) + SESSION.commit() + return True + + SESSION.close() + return False + + +def toggle_afk(user_id, reason=""): + with INSERTION_LOCK: + curr = SESSION.query(AFK).get(user_id) + if not curr: + curr = AFK(user_id, reason, True) + elif curr.is_afk: + curr.is_afk = False + elif not curr.is_afk: + curr.is_afk = True + SESSION.add(curr) + SESSION.commit() + + +def __load_afk_users(): + global AFK_USERS + try: + all_afk = SESSION.query(AFK).all() + AFK_USERS = { + user.user_id: {"reason": user.reason, "time": user.time} + for user in all_afk + if user.is_afk + } + finally: + SESSION.close() + + +__load_afk_users() diff --git a/Database/sql/antichannel_sql.py b/Database/sql/antichannel_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..a6042e5f8faec7a119571b709a54db2228e052e8 --- /dev/null +++ b/Database/sql/antichannel_sql.py @@ -0,0 +1,88 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Boolean, Column +from sqlalchemy.sql.sqltypes import String + +from Database.sql import BASE, SESSION + + +class AntiChannelSettings(BASE): + __tablename__ = "anti_channel_settings" + + chat_id = Column(String(14), primary_key=True) + setting = Column(Boolean, default=False, nullable=False) + + def __init__(self, chat_id: int, disabled: bool): + self.chat_id = str(chat_id) + self.setting = disabled + + def __repr__(self): + return "<ᴀɴᴛɪғʟᴏᴏᴅ sᴇᴛᴛɪɴɢ {} ({})>".format(self.chat_id, self.setting) + + +AntiChannelSettings.__table__.create(checkfirst=True) +ANTICHANNEL_SETTING_LOCK = threading.RLock() + + +def enable_antichannel(chat_id: int): + with ANTICHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiChannelSettings(str(chat_id), True) + + chat.setting = True + SESSION.add(chat) + SESSION.commit() + + +def disable_antichannel(chat_id: int): + with ANTICHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiChannelSettings(str(chat_id), False) + + chat.setting = False + SESSION.add(chat) + SESSION.commit() + + +def antichannel_status(chat_id: int) -> bool: + with ANTICHANNEL_SETTING_LOCK: + d = SESSION.query(AntiChannelSettings).get(str(chat_id)) + if not d: + return False + return d.setting + + +def migrate_chat(old_chat_id, new_chat_id): + with ANTICHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiChannelSettings).get(str(old_chat_id)) + if chat: + chat.chat_id = new_chat_id + SESSION.add(chat) + + SESSION.commit() diff --git a/Database/sql/antiflood_sql.py b/Database/sql/antiflood_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..f8a5de981fadbefb7b365c975b9f02785542cc41 --- /dev/null +++ b/Database/sql/antiflood_sql.py @@ -0,0 +1,171 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Column, String, UnicodeText + +from Database.sql import BASE, SESSION + +DEF_COUNT = 1 +DEF_LIMIT = 0 +DEF_OBJ = (None, DEF_COUNT, DEF_LIMIT) + + +class FloodControl(BASE): + __tablename__ = "antiflood" + chat_id = Column(String(14), primary_key=True) + user_id = Column(BigInteger) + count = Column(BigInteger, default=DEF_COUNT) + limit = Column(BigInteger, default=DEF_LIMIT) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) # ensure string + + def __repr__(self): + return "<ғʟᴏᴏᴅ ᴄᴏɴᴛʀᴏʟ ғᴏʀ %s>" % self.chat_id + + +class FloodSettings(BASE): + __tablename__ = "antiflood_settings" + chat_id = Column(String(14), primary_key=True) + flood_type = Column(BigInteger, default=1) + value = Column(UnicodeText, default="0") + + def __init__(self, chat_id, flood_type=1, value="0"): + self.chat_id = str(chat_id) + self.flood_type = flood_type + self.value = value + + def __repr__(self): + return "<{} ᴡɪʟʟ ᴇxᴇᴄᴜᴛɪɴɢ {} ғᴏʀ ғʟᴏᴏᴅ.>".format(self.chat_id, self.flood_type) + + +FloodControl.__table__.create(checkfirst=True) +FloodSettings.__table__.create(checkfirst=True) + +INSERTION_FLOOD_LOCK = threading.RLock() +INSERTION_FLOOD_SETTINGS_LOCK = threading.RLock() + +CHAT_FLOOD = {} + + +def set_flood(chat_id, amount): + with INSERTION_FLOOD_LOCK: + flood = SESSION.query(FloodControl).get(str(chat_id)) + if not flood: + flood = FloodControl(str(chat_id)) + + flood.user_id = None + flood.limit = amount + + CHAT_FLOOD[str(chat_id)] = (None, DEF_COUNT, amount) + + SESSION.add(flood) + SESSION.commit() + + +def update_flood(chat_id: str, user_id) -> bool: + if str(chat_id) not in CHAT_FLOOD: + return + + curr_user_id, count, limit = CHAT_FLOOD.get(str(chat_id), DEF_OBJ) + + if limit == 0: # no antiflood + return False + + if user_id != curr_user_id or user_id is None: # other user + CHAT_FLOOD[str(chat_id)] = (user_id, DEF_COUNT, limit) + return False + + count += 1 + if count > limit: # too many msgs, kick + CHAT_FLOOD[str(chat_id)] = (None, DEF_COUNT, limit) + return True + + # default -> update + CHAT_FLOOD[str(chat_id)] = (user_id, count, limit) + return False + + +def get_flood_limit(chat_id): + return CHAT_FLOOD.get(str(chat_id), DEF_OBJ)[2] + + +def set_flood_strength(chat_id, flood_type, value): + # ғᴏʀ ғʟᴏᴏᴅ_ᴛʏᴘᴇ + # 1 = ban + # 2 = kick + # 3 = mute + # 4 = tban + # 5 = tmute + # 6 = ᴅᴍᴜᴛᴇ sᴏᴏɴ + with INSERTION_FLOOD_SETTINGS_LOCK: + curr_setting = SESSION.query(FloodSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = FloodSettings( + chat_id, + flood_type=int(flood_type), + value=value, + ) + + curr_setting.flood_type = int(flood_type) + curr_setting.value = str(value) + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_flood_setting(chat_id): + try: + setting = SESSION.query(FloodSettings).get(str(chat_id)) + if setting: + return setting.flood_type, setting.value + return 1, "0" + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_FLOOD_LOCK: + flood = SESSION.query(FloodControl).get(str(old_chat_id)) + if flood: + CHAT_FLOOD[str(new_chat_id)] = CHAT_FLOOD.get(str(old_chat_id), DEF_OBJ) + flood.chat_id = str(new_chat_id) + SESSION.commit() + + SESSION.close() + + +def __load_flood_settings(): + global CHAT_FLOOD + try: + all_chats = SESSION.query(FloodControl).all() + CHAT_FLOOD = {chat.chat_id: (None, DEF_COUNT, chat.limit) for chat in all_chats} + finally: + SESSION.close() + + +__load_flood_settings() diff --git a/Database/sql/antilinkedchannel_sql.py b/Database/sql/antilinkedchannel_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..0b7bce3cba6cc77e729ec19f76f39623b4adfa5a --- /dev/null +++ b/Database/sql/antilinkedchannel_sql.py @@ -0,0 +1,142 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Boolean, Column +from sqlalchemy.sql.sqltypes import String + +from Database.sql import BASE, SESSION + + +class AntiLinkedChannelSettings(BASE): + __tablename__ = "anti_linked_channel_settings" + + chat_id = Column(String(14), primary_key=True) + setting = Column(Boolean, default=False, nullable=False) + + def __init__(self, chat_id: int, disabled: bool): + self.chat_id = str(chat_id) + self.setting = disabled + + def __repr__(self): + return "".format(self.chat_id, self.setting) + + +class AntiPinChannelSettings(BASE): + __tablename__ = "anti_pin_channel_settings" + + chat_id = Column(String(14), primary_key=True) + setting = Column(Boolean, default=False, nullable=False) + + def __init__(self, chat_id: int, disabled: bool): + self.chat_id = str(chat_id) + self.setting = disabled + + def __repr__(self): + return "<ᴀɴᴛɪᴘɪɴ sᴇᴛᴛɪɴɢ {} ({})>".format(self.chat_id, self.setting) + + +AntiLinkedChannelSettings.__table__.create(checkfirst=True) +ANTI_LINKED_CHANNEL_SETTING_LOCK = threading.RLock() + +AntiPinChannelSettings.__table__.create(checkfirst=True) +ANTI_PIN_CHANNEL_SETTING_LOCK = threading.RLock() + + +def enable_linked(chat_id: int): + with ANTI_LINKED_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiLinkedChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiLinkedChannelSettings(chat_id, True) + + chat.setting = True + SESSION.add(chat) + SESSION.commit() + + +def enable_pin(chat_id: int): + with ANTI_PIN_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiPinChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiPinChannelSettings(chat_id, True) + + chat.setting = True + SESSION.add(chat) + SESSION.commit() + + +def disable_linked(chat_id: int): + with ANTI_LINKED_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiLinkedChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiLinkedChannelSettings(chat_id, False) + + chat.setting = False + SESSION.add(chat) + SESSION.commit() + + +def disable_pin(chat_id: int): + with ANTI_PIN_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiPinChannelSettings).get(str(chat_id)) + if not chat: + chat = AntiPinChannelSettings(chat_id, False) + + chat.setting = False + SESSION.add(chat) + SESSION.commit() + + +def status_linked(chat_id: int) -> bool: + with ANTI_LINKED_CHANNEL_SETTING_LOCK: + d = SESSION.query(AntiLinkedChannelSettings).get(str(chat_id)) + if not d: + return False + return d.setting + + +def status_pin(chat_id: int) -> bool: + with ANTI_PIN_CHANNEL_SETTING_LOCK: + d = SESSION.query(AntiPinChannelSettings).get(str(chat_id)) + if not d: + return False + return d.setting + + +def migrate_chat(old_chat_id, new_chat_id): + with ANTI_LINKED_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiLinkedChannelSettings).get(str(old_chat_id)) + if chat: + chat.chat_id = new_chat_id + SESSION.add(chat) + + SESSION.commit() + with ANTI_PIN_CHANNEL_SETTING_LOCK: + chat = SESSION.query(AntiPinChannelSettings).get(str(old_chat_id)) + if chat: + chat.chat_id = new_chat_id + SESSION.add(chat) + + SESSION.commit() diff --git a/Database/sql/approve_sql.py b/Database/sql/approve_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..45caa328e734454d01aba9b4245b3fb6bafd16ec --- /dev/null +++ b/Database/sql/approve_sql.py @@ -0,0 +1,85 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Column, String + +from Database.sql import BASE, SESSION + + +class Approvals(BASE): + __tablename__ = "approval" + chat_id = Column(String(14), primary_key=True) + user_id = Column(BigInteger, primary_key=True) + + def __init__(self, chat_id, user_id): + self.chat_id = str(chat_id) # ensure string + self.user_id = user_id + + def __repr__(self): + return "<ᴀᴘᴘʀᴏᴠᴇ %s>" % self.user_id + + +Approvals.__table__.create(checkfirst=True) + +APPROVE_INSERTION_LOCK = threading.RLock() + + +def approve(chat_id, user_id): + with APPROVE_INSERTION_LOCK: + approve_user = Approvals(str(chat_id), user_id) + SESSION.add(approve_user) + SESSION.commit() + + +def is_approved(chat_id, user_id): + try: + return SESSION.query(Approvals).get((str(chat_id), user_id)) + finally: + SESSION.close() + + +def disapprove(chat_id, user_id): + with APPROVE_INSERTION_LOCK: + disapprove_user = SESSION.query(Approvals).get((str(chat_id), user_id)) + if disapprove_user: + SESSION.delete(disapprove_user) + SESSION.commit() + return True + else: + SESSION.close() + return False + + +def list_approved(chat_id): + try: + return ( + SESSION.query(Approvals) + .filter(Approvals.chat_id == str(chat_id)) + .order_by(Approvals.user_id.asc()) + .all() + ) + finally: + SESSION.close() diff --git a/Database/sql/blacklist_sql.py b/Database/sql/blacklist_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..d33038496b7b136a014a1e82780d3e3d701e99d3 --- /dev/null +++ b/Database/sql/blacklist_sql.py @@ -0,0 +1,223 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Column, String, UnicodeText, distinct, func + +from Database.sql import BASE, SESSION + + +class BlackListFilters(BASE): + __tablename__ = "blacklist" + chat_id = Column(String(14), primary_key=True) + trigger = Column(UnicodeText, primary_key=True, nullable=False) + + def __init__(self, chat_id, trigger): + self.chat_id = str(chat_id) # ensure string + self.trigger = trigger + + def __repr__(self): + return "<ʙʟᴀᴄᴋʟɪsᴛ ғɪʟᴛᴇʀ '%s' ғᴏʀ %s>" % (self.trigger, self.chat_id) + + def __eq__(self, other): + return bool( + isinstance(other, BlackListFilters) + and self.chat_id == other.chat_id + and self.trigger == other.trigger, + ) + + +class BlacklistSettings(BASE): + __tablename__ = "blacklist_settings" + chat_id = Column(String(14), primary_key=True) + blacklist_type = Column(BigInteger, default=1) + value = Column(UnicodeText, default="0") + + def __init__(self, chat_id, blacklist_type=1, value="0"): + self.chat_id = str(chat_id) + self.blacklist_type = blacklist_type + self.value = value + + def __repr__(self): + return "<{} ᴡɪʟʟ ᴇxᴇᴄᴜᴛɪɴɢ {} ғᴏʀ ʙʟᴀᴄᴋʟɪsᴛ ᴛʀɪɢɢᴇʀ.>".format( + self.chat_id, + self.blacklist_type, + ) + + +BlackListFilters.__table__.create(checkfirst=True) +BlacklistSettings.__table__.create(checkfirst=True) + +BLACKLIST_FILTER_INSERTION_LOCK = threading.RLock() +BLACKLIST_SETTINGS_INSERTION_LOCK = threading.RLock() + +CHAT_BLACKLISTS = {} +CHAT_SETTINGS_BLACKLISTS = {} + + +def add_to_blacklist(chat_id, trigger): + with BLACKLIST_FILTER_INSERTION_LOCK: + blacklist_filt = BlackListFilters(str(chat_id), trigger) + + SESSION.merge(blacklist_filt) # merge to avoid duplicate key issues + SESSION.commit() + global CHAT_BLACKLISTS + if CHAT_BLACKLISTS.get(str(chat_id), set()) == set(): + CHAT_BLACKLISTS[str(chat_id)] = {trigger} + else: + CHAT_BLACKLISTS.get(str(chat_id), set()).add(trigger) + + +def rm_from_blacklist(chat_id, trigger): + with BLACKLIST_FILTER_INSERTION_LOCK: + blacklist_filt = SESSION.query(BlackListFilters).get((str(chat_id), trigger)) + if blacklist_filt: + if trigger in CHAT_BLACKLISTS.get(str(chat_id), set()): # sanity check + CHAT_BLACKLISTS.get(str(chat_id), set()).remove(trigger) + + SESSION.delete(blacklist_filt) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_chat_blacklist(chat_id): + return CHAT_BLACKLISTS.get(str(chat_id), set()) + + +def num_blacklist_filters(): + try: + return SESSION.query(BlackListFilters).count() + finally: + SESSION.close() + + +def num_blacklist_chat_filters(chat_id): + try: + return ( + SESSION.query(BlackListFilters.chat_id) + .filter(BlackListFilters.chat_id == str(chat_id)) + .count() + ) + finally: + SESSION.close() + + +def num_blacklist_filter_chats(): + try: + return SESSION.query(func.count(distinct(BlackListFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def set_blacklist_strength(chat_id, blacklist_type, value): + # for blacklist_type + # 0 = nothing + # 1 = delete + # 2 = warn + # 3 = mute + # 4 = kick + # 5 = ban + # 6 = tban + # 7 = tmute + with BLACKLIST_SETTINGS_INSERTION_LOCK: + global CHAT_SETTINGS_BLACKLISTS + curr_setting = SESSION.query(BlacklistSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = BlacklistSettings( + chat_id, + blacklist_type=int(blacklist_type), + value=value, + ) + + curr_setting.blacklist_type = int(blacklist_type) + curr_setting.value = str(value) + CHAT_SETTINGS_BLACKLISTS[str(chat_id)] = { + "blacklist_type": int(blacklist_type), + "value": value, + } + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_blacklist_setting(chat_id): + try: + setting = CHAT_SETTINGS_BLACKLISTS.get(str(chat_id)) + if setting: + return setting["blacklist_type"], setting["value"] + return 1, "0" + + finally: + SESSION.close() + + +def __load_chat_blacklists(): + global CHAT_BLACKLISTS + try: + chats = SESSION.query(BlackListFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + CHAT_BLACKLISTS[chat_id] = [] + + all_filters = SESSION.query(BlackListFilters).all() + for x in all_filters: + CHAT_BLACKLISTS[x.chat_id] += [x.trigger] + + CHAT_BLACKLISTS = {x: set(y) for x, y in CHAT_BLACKLISTS.items()} + + finally: + SESSION.close() + + +def __load_chat_settings_blacklists(): + global CHAT_SETTINGS_BLACKLISTS + try: + chats_settings = SESSION.query(BlacklistSettings).all() + for x in chats_settings: # remove tuple by ( ,) + CHAT_SETTINGS_BLACKLISTS[x.chat_id] = { + "blacklist_type": x.blacklist_type, + "value": x.value, + } + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with BLACKLIST_FILTER_INSERTION_LOCK: + chat_filters = ( + SESSION.query(BlackListFilters) + .filter(BlackListFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_chat_blacklists() +__load_chat_settings_blacklists() diff --git a/Database/sql/blacklistusers_sql.py b/Database/sql/blacklistusers_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..7b0115f583b7353538367b693c1a03d69dcd7603 --- /dev/null +++ b/Database/sql/blacklistusers_sql.py @@ -0,0 +1,90 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Column, String, UnicodeText + +from Database.sql import BASE, SESSION + + +class BlacklistUsers(BASE): + __tablename__ = "blacklistusers" + user_id = Column(String(14), primary_key=True) + reason = Column(UnicodeText) + + def __init__(self, user_id, reason=None): + self.user_id = user_id + self.reason = reason + + +BlacklistUsers.__table__.create(checkfirst=True) + +BLACKLIST_LOCK = threading.RLock() +BLACKLIST_USERS = set() + + +def blacklist_user(user_id, reason=None): + with BLACKLIST_LOCK: + user = SESSION.query(BlacklistUsers).get(str(user_id)) + if not user: + user = BlacklistUsers(str(user_id), reason) + else: + user.reason = reason + + SESSION.add(user) + SESSION.commit() + __load_blacklist_userid_list() + + +def unblacklist_user(user_id): + with BLACKLIST_LOCK: + user = SESSION.query(BlacklistUsers).get(str(user_id)) + if user: + SESSION.delete(user) + + SESSION.commit() + __load_blacklist_userid_list() + + +def get_reason(user_id): + user = SESSION.query(BlacklistUsers).get(str(user_id)) + rep = user.reason if user else "" + SESSION.close() + return rep + + +def is_user_blacklisted(user_id): + return user_id in BLACKLIST_USERS + + +def __load_blacklist_userid_list(): + global BLACKLIST_USERS + try: + BLACKLIST_USERS = {int(x.user_id) for x in SESSION.query(BlacklistUsers).all()} + finally: + SESSION.close() + + +__load_blacklist_userid_list() diff --git a/Database/sql/blsticker_sql.py b/Database/sql/blsticker_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..897ca46b34c5736d44cd176ac981b188d8c17344 --- /dev/null +++ b/Database/sql/blsticker_sql.py @@ -0,0 +1,223 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Column, String, UnicodeText, distinct, func + +from Database.sql import BASE, SESSION + + +class StickersFilters(BASE): + __tablename__ = "blacklist_stickers" + chat_id = Column(String(14), primary_key=True) + trigger = Column(UnicodeText, primary_key=True, nullable=False) + + def __init__(self, chat_id, trigger): + self.chat_id = str(chat_id) # ensure string + self.trigger = trigger + + def __repr__(self): + return "" % (self.trigger, self.chat_id) + + def __eq__(self, other): + return bool( + isinstance(other, StickersFilters) + and self.chat_id == other.chat_id + and self.trigger == other.trigger, + ) + + +class StickerSettings(BASE): + __tablename__ = "blsticker_settings" + chat_id = Column(String(14), primary_key=True) + blacklist_type = Column(BigInteger, default=1) + value = Column(UnicodeText, default="0") + + def __init__(self, chat_id, blacklist_type=1, value="0"): + self.chat_id = str(chat_id) + self.blacklist_type = blacklist_type + self.value = value + + def __repr__(self): + return "<{} ᴡɪʟʟ ᴇxᴇᴄᴜᴛɪɴɢ {} ғᴏʀ ʙʟᴀᴄᴋʟɪsᴛ ᴛʀɪɢɢᴇʀ.>".format( + self.chat_id, + self.blacklist_type, + ) + + +StickersFilters.__table__.create(checkfirst=True) +StickerSettings.__table__.create(checkfirst=True) + +STICKERS_FILTER_INSERTION_LOCK = threading.RLock() +STICKSET_FILTER_INSERTION_LOCK = threading.RLock() + +CHAT_STICKERS = {} +CHAT_BLSTICK_BLACKLISTS = {} + + +def add_to_stickers(chat_id, trigger): + with STICKERS_FILTER_INSERTION_LOCK: + stickers_filt = StickersFilters(str(chat_id), trigger) + + SESSION.merge(stickers_filt) # merge to avoid duplicate key issues + SESSION.commit() + global CHAT_STICKERS + if CHAT_STICKERS.get(str(chat_id), set()) == set(): + CHAT_STICKERS[str(chat_id)] = {trigger} + else: + CHAT_STICKERS.get(str(chat_id), set()).add(trigger) + + +def rm_from_stickers(chat_id, trigger): + with STICKERS_FILTER_INSERTION_LOCK: + stickers_filt = SESSION.query(StickersFilters).get((str(chat_id), trigger)) + if stickers_filt: + if trigger in CHAT_STICKERS.get(str(chat_id), set()): # sanity check + CHAT_STICKERS.get(str(chat_id), set()).remove(trigger) + + SESSION.delete(stickers_filt) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_chat_stickers(chat_id): + return CHAT_STICKERS.get(str(chat_id), set()) + + +def num_stickers_filters(): + try: + return SESSION.query(StickersFilters).count() + finally: + SESSION.close() + + +def num_stickers_chat_filters(chat_id): + try: + return ( + SESSION.query(StickersFilters.chat_id) + .filter(StickersFilters.chat_id == str(chat_id)) + .count() + ) + finally: + SESSION.close() + + +def num_stickers_filter_chats(): + try: + return SESSION.query(func.count(distinct(StickersFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def set_blacklist_strength(chat_id, blacklist_type, value): + # for blacklist_type + # 0 = nothing + # 1 = delete + # 2 = warn + # 3 = mute + # 4 = kick + # 5 = ban + # 6 = tban + # 7 = tmute + with STICKSET_FILTER_INSERTION_LOCK: + global CHAT_BLSTICK_BLACKLISTS + curr_setting = SESSION.query(StickerSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = StickerSettings( + chat_id, + blacklist_type=int(blacklist_type), + value=value, + ) + + curr_setting.blacklist_type = int(blacklist_type) + curr_setting.value = str(value) + CHAT_BLSTICK_BLACKLISTS[str(chat_id)] = { + "blacklist_type": int(blacklist_type), + "value": value, + } + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_blacklist_setting(chat_id): + try: + setting = CHAT_BLSTICK_BLACKLISTS.get(str(chat_id)) + if setting: + return setting["blacklist_type"], setting["value"] + return 1, "0" + + finally: + SESSION.close() + + +def __load_CHAT_STICKERS(): + global CHAT_STICKERS + try: + chats = SESSION.query(StickersFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + CHAT_STICKERS[chat_id] = [] + + all_filters = SESSION.query(StickersFilters).all() + for x in all_filters: + CHAT_STICKERS[x.chat_id] += [x.trigger] + + CHAT_STICKERS = {x: set(y) for x, y in CHAT_STICKERS.items()} + + finally: + SESSION.close() + + +def __load_chat_stickerset_blacklists(): + global CHAT_BLSTICK_BLACKLISTS + try: + chats_settings = SESSION.query(StickerSettings).all() + for x in chats_settings: # remove tuple by ( ,) + CHAT_BLSTICK_BLACKLISTS[x.chat_id] = { + "blacklist_type": x.blacklist_type, + "value": x.value, + } + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with STICKERS_FILTER_INSERTION_LOCK: + chat_filters = ( + SESSION.query(StickersFilters) + .filter(StickersFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_CHAT_STICKERS() +__load_chat_stickerset_blacklists() diff --git a/Database/sql/cleaner_sql.py b/Database/sql/cleaner_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..40744a3d86a1ff407aec75c8b8c76314d2305adc --- /dev/null +++ b/Database/sql/cleaner_sql.py @@ -0,0 +1,225 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Boolean, Column, UnicodeText + +from Database.sql import BASE, SESSION + + +class CleanerBlueTextChatSettings(BASE): + __tablename__ = "cleaner_bluetext_chat_setting" + chat_id = Column(UnicodeText, primary_key=True) + is_enable = Column(Boolean, default=False) + + def __init__(self, chat_id, is_enable): + self.chat_id = chat_id + self.is_enable = is_enable + + def __repr__(self): + return "ᴄʟᴇᴀɴ ʙʟᴜᴇ ᴛᴇxᴛ ғᴏʀ {}".format(self.chat_id) + + +class CleanerBlueTextChat(BASE): + __tablename__ = "cleaner_bluetext_chat_ignore_commands" + chat_id = Column(UnicodeText, primary_key=True) + command = Column(UnicodeText, primary_key=True) + + def __init__(self, chat_id, command): + self.chat_id = chat_id + self.command = command + + +class CleanerBlueTextGlobal(BASE): + __tablename__ = "cleaner_bluetext_global_ignore_commands" + command = Column(UnicodeText, primary_key=True) + + def __init__(self, command): + self.command = command + + +CleanerBlueTextChatSettings.__table__.create(checkfirst=True) +CleanerBlueTextChat.__table__.create(checkfirst=True) +CleanerBlueTextGlobal.__table__.create(checkfirst=True) + +CLEANER_CHAT_SETTINGS = threading.RLock() +CLEANER_CHAT_LOCK = threading.RLock() +CLEANER_GLOBAL_LOCK = threading.RLock() + +CLEANER_CHATS = {} +GLOBAL_IGNORE_COMMANDS = set() + + +def set_cleanbt(chat_id, is_enable): + with CLEANER_CHAT_SETTINGS: + curr = SESSION.query(CleanerBlueTextChatSettings).get(str(chat_id)) + + if not curr: + curr = CleanerBlueTextChatSettings(str(chat_id), is_enable) + else: + curr.is_enabled = is_enable + + if str(chat_id) not in CLEANER_CHATS: + CLEANER_CHATS.setdefault( + str(chat_id), {"setting": False, "commands": set()} + ) + + CLEANER_CHATS[str(chat_id)]["setting"] = is_enable + + SESSION.add(curr) + SESSION.commit() + + +def chat_ignore_command(chat_id, ignore): + ignore = ignore.lower() + with CLEANER_CHAT_LOCK: + ignored = SESSION.query(CleanerBlueTextChat).get((str(chat_id), ignore)) + + if not ignored: + if str(chat_id) not in CLEANER_CHATS: + CLEANER_CHATS.setdefault( + str(chat_id), {"setting": False, "commands": set()} + ) + + CLEANER_CHATS[str(chat_id)]["commands"].add(ignore) + + ignored = CleanerBlueTextChat(str(chat_id), ignore) + SESSION.add(ignored) + SESSION.commit() + return True + SESSION.close() + return False + + +def chat_unignore_command(chat_id, unignore): + unignore = unignore.lower() + with CLEANER_CHAT_LOCK: + unignored = SESSION.query(CleanerBlueTextChat).get((str(chat_id), unignore)) + + if unignored: + if str(chat_id) not in CLEANER_CHATS: + CLEANER_CHATS.setdefault( + str(chat_id), {"setting": False, "commands": set()} + ) + if unignore in CLEANER_CHATS.get(str(chat_id)).get("commands"): + CLEANER_CHATS[str(chat_id)]["commands"].remove(unignore) + + SESSION.delete(unignored) + SESSION.commit() + return True + + SESSION.close() + return False + + +def global_ignore_command(command): + command = command.lower() + with CLEANER_GLOBAL_LOCK: + ignored = SESSION.query(CleanerBlueTextGlobal).get(str(command)) + + if not ignored: + GLOBAL_IGNORE_COMMANDS.add(command) + + ignored = CleanerBlueTextGlobal(str(command)) + SESSION.add(ignored) + SESSION.commit() + return True + + SESSION.close() + return False + + +def global_unignore_command(command): + command = command.lower() + with CLEANER_GLOBAL_LOCK: + unignored = SESSION.query(CleanerBlueTextGlobal).get(str(command)) + + if unignored: + if command in GLOBAL_IGNORE_COMMANDS: + GLOBAL_IGNORE_COMMANDS.remove(command) + + SESSION.delete(command) + SESSION.commit() + return True + + SESSION.close() + return False + + +def is_command_ignored(chat_id, command): + if command.lower() in GLOBAL_IGNORE_COMMANDS: + return True + + if str(chat_id) in CLEANER_CHATS and command.lower() in CLEANER_CHATS.get( + str(chat_id) + ).get("commands"): + return True + + return False + + +def is_enabled(chat_id): + if str(chat_id) in CLEANER_CHATS: + return CLEANER_CHATS.get(str(chat_id)).get("setting") + + return False + + +def get_all_ignored(chat_id): + if str(chat_id) in CLEANER_CHATS: + LOCAL_IGNORE_COMMANDS = CLEANER_CHATS.get(str(chat_id)).get("commands") + else: + LOCAL_IGNORE_COMMANDS = set() + + return GLOBAL_IGNORE_COMMANDS, LOCAL_IGNORE_COMMANDS + + +def __load_cleaner_list(): + global GLOBAL_IGNORE_COMMANDS + global CLEANER_CHATS + + try: + GLOBAL_IGNORE_COMMANDS = { + x.command for x in SESSION.query(CleanerBlueTextGlobal).all() + } + finally: + SESSION.close() + + try: + for x in SESSION.query(CleanerBlueTextChatSettings).all(): + CLEANER_CHATS.setdefault(x.chat_id, {"setting": False, "commands": set()}) + CLEANER_CHATS[x.chat_id]["setting"] = x.is_enable + finally: + SESSION.close() + + try: + for x in SESSION.query(CleanerBlueTextChat).all(): + CLEANER_CHATS.setdefault(x.chat_id, {"setting": False, "commands": set()}) + CLEANER_CHATS[x.chat_id]["commands"].add(x.command) + finally: + SESSION.close() + + +__load_cleaner_list() diff --git a/Database/sql/connection_sql.py b/Database/sql/connection_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..71f6ea97c1110a8b8a64fb716c2a5c3b59046a84 --- /dev/null +++ b/Database/sql/connection_sql.py @@ -0,0 +1,230 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading +import time +from typing import Union + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText + +from Database.sql import BASE, SESSION + + +class ChatAccessConnectionSettings(BASE): + __tablename__ = "access_connection" + chat_id = Column(String(14), primary_key=True) + allow_connect_to_chat = Column(Boolean, default=True) + + def __init__(self, chat_id, allow_connect_to_chat): + self.chat_id = str(chat_id) + self.allow_connect_to_chat = str(allow_connect_to_chat) + + def __repr__(self): + return "<ᴄʜᴀᴛ ᴀᴄᴄᴇss sᴇᴛᴛɪɴɢs ({}) is {}>".format( + self.chat_id, + self.allow_connect_to_chat, + ) + + +class Connection(BASE): + __tablename__ = "connection" + user_id = Column(BigInteger, primary_key=True) + chat_id = Column(String(14)) + + def __init__(self, user_id, chat_id): + self.user_id = user_id + self.chat_id = str(chat_id) # Ensure String + + +class ConnectionHistory(BASE): + __tablename__ = "connection_history" + user_id = Column(BigInteger, primary_key=True) + chat_id = Column(String(14), primary_key=True) + chat_name = Column(UnicodeText) + conn_time = Column(BigInteger) + + def __init__(self, user_id, chat_id, chat_name, conn_time): + self.user_id = user_id + self.chat_id = str(chat_id) + self.chat_name = str(chat_name) + self.conn_time = int(conn_time) + + def __repr__(self): + return "<ᴄᴏɴɴᴇᴄᴛɪᴏɴ ᴜsᴇʀ {} ʜɪsᴛᴏʀʏ {}>".format(self.user_id, self.chat_id) + + +ChatAccessConnectionSettings.__table__.create(checkfirst=True) +Connection.__table__.create(checkfirst=True) +ConnectionHistory.__table__.create(checkfirst=True) + +CHAT_ACCESS_LOCK = threading.RLock() +CONNECTION_INSERTION_LOCK = threading.RLock() +CONNECTION_HISTORY_LOCK = threading.RLock() + +HISTORY_CONNECT = {} + + +def allow_connect_to_chat(chat_id: Union[str, int]) -> bool: + try: + chat_setting = SESSION.query(ChatAccessConnectionSettings).get(str(chat_id)) + if chat_setting: + return chat_setting.allow_connect_to_chat + return False + finally: + SESSION.close() + + +def set_allow_connect_to_chat(chat_id: Union[int, str], setting: bool): + with CHAT_ACCESS_LOCK: + chat_setting = SESSION.query(ChatAccessConnectionSettings).get(str(chat_id)) + if not chat_setting: + chat_setting = ChatAccessConnectionSettings(chat_id, setting) + + chat_setting.allow_connect_to_chat = setting + SESSION.add(chat_setting) + SESSION.commit() + + +def connect(user_id, chat_id): + with CONNECTION_INSERTION_LOCK: + prev = SESSION.query(Connection).get((int(user_id))) + if prev: + SESSION.delete(prev) + connect_to_chat = Connection(int(user_id), chat_id) + SESSION.add(connect_to_chat) + SESSION.commit() + return True + + +def get_connected_chat(user_id): + try: + return SESSION.query(Connection).get((int(user_id))) + finally: + SESSION.close() + + +def curr_connection(chat_id): + try: + return SESSION.query(Connection).get((str(chat_id))) + finally: + SESSION.close() + + +def disconnect(user_id): + with CONNECTION_INSERTION_LOCK: + disconnect = SESSION.query(Connection).get((int(user_id))) + if disconnect: + SESSION.delete(disconnect) + SESSION.commit() + return True + SESSION.close() + return False + + +def add_history_conn(user_id, chat_id, chat_name): + global HISTORY_CONNECT + with CONNECTION_HISTORY_LOCK: + conn_time = int(time.time()) + if HISTORY_CONNECT.get(int(user_id)): + counting = ( + SESSION.query(ConnectionHistory.user_id) + .filter(ConnectionHistory.user_id == str(user_id)) + .count() + ) + getchat_id = { + HISTORY_CONNECT[int(user_id)][x]["chat_id"]: x + for x in HISTORY_CONNECT[int(user_id)] + } + + if chat_id in getchat_id: + todeltime = getchat_id[str(chat_id)] + delold = SESSION.query(ConnectionHistory).get( + (int(user_id), str(chat_id)), + ) + if delold: + SESSION.delete(delold) + HISTORY_CONNECT[int(user_id)].pop(todeltime) + elif counting >= 5: + todel = list(HISTORY_CONNECT[int(user_id)]) + todel.reverse() + todel = todel[4:] + for x in todel: + chat_old = HISTORY_CONNECT[int(user_id)][x]["chat_id"] + delold = SESSION.query(ConnectionHistory).get( + (int(user_id), str(chat_old)), + ) + if delold: + SESSION.delete(delold) + HISTORY_CONNECT[int(user_id)].pop(x) + else: + HISTORY_CONNECT[int(user_id)] = {} + delold = SESSION.query(ConnectionHistory).get((int(user_id), str(chat_id))) + if delold: + SESSION.delete(delold) + history = ConnectionHistory(int(user_id), str(chat_id), chat_name, conn_time) + SESSION.add(history) + SESSION.commit() + HISTORY_CONNECT[int(user_id)][conn_time] = { + "chat_name": chat_name, + "chat_id": str(chat_id), + } + + +def get_history_conn(user_id): + if not HISTORY_CONNECT.get(int(user_id)): + HISTORY_CONNECT[int(user_id)] = {} + return HISTORY_CONNECT[int(user_id)] + + +def clear_history_conn(user_id): + global HISTORY_CONNECT + todel = list(HISTORY_CONNECT[int(user_id)]) + for x in todel: + chat_old = HISTORY_CONNECT[int(user_id)][x]["chat_id"] + delold = SESSION.query(ConnectionHistory).get((int(user_id), str(chat_old))) + if delold: + SESSION.delete(delold) + HISTORY_CONNECT[int(user_id)].pop(x) + SESSION.commit() + return True + + +def __load_user_history(): + global HISTORY_CONNECT + try: + qall = SESSION.query(ConnectionHistory).all() + HISTORY_CONNECT = {} + for x in qall: + check = HISTORY_CONNECT.get(x.user_id) + if check is None: + HISTORY_CONNECT[x.user_id] = {} + HISTORY_CONNECT[x.user_id][x.conn_time] = { + "chat_name": x.chat_name, + "chat_id": x.chat_id, + } + finally: + SESSION.close() + + +__load_user_history() diff --git a/Database/sql/cust_filters_sql.py b/Database/sql/cust_filters_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..65b1b2a6f04deaf411a0dbcacae6a9dca5be96d4 --- /dev/null +++ b/Database/sql/cust_filters_sql.py @@ -0,0 +1,404 @@ +import threading + +from sqlalchemy import Boolean, Column, Integer, String, UnicodeText, distinct, func + +from Database.sql import BASE, SESSION +from Mikobot.plugins.helper_funcs.msg_types import Types + + +class CustomFilters(BASE): + __tablename__ = "cust_filters" + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True, nullable=False) + reply = Column(UnicodeText, nullable=False) + is_sticker = Column(Boolean, nullable=False, default=False) + is_document = Column(Boolean, nullable=False, default=False) + is_image = Column(Boolean, nullable=False, default=False) + is_audio = Column(Boolean, nullable=False, default=False) + is_voice = Column(Boolean, nullable=False, default=False) + is_video = Column(Boolean, nullable=False, default=False) + + has_buttons = Column(Boolean, nullable=False, default=False) + # NOTE: Here for legacy purposes, to ensure older filters don't mess up. + has_markdown = Column(Boolean, nullable=False, default=False) + + # NEW FILTER + # alter table cust_filters add column reply_text text; + # alter table cust_filters add column file_type integer default 1; + # alter table cust_filters add column file_id text; + reply_text = Column(UnicodeText) + file_type = Column(Integer, nullable=False, default=1) + file_id = Column(UnicodeText, default=None) + + def __init__( + self, + chat_id, + keyword, + reply, + is_sticker=False, + is_document=False, + is_image=False, + is_audio=False, + is_voice=False, + is_video=False, + has_buttons=False, + reply_text=None, + file_type=1, + file_id=None, + ): + self.chat_id = str(chat_id) # ensure string + self.keyword = keyword + self.reply = reply + self.is_sticker = is_sticker + self.is_document = is_document + self.is_image = is_image + self.is_audio = is_audio + self.is_voice = is_voice + self.is_video = is_video + self.has_buttons = has_buttons + self.has_markdown = True + + self.reply_text = reply_text + self.file_type = file_type + self.file_id = file_id + + def __repr__(self): + return "" % self.chat_id + + def __eq__(self, other): + return bool( + isinstance(other, CustomFilters) + and self.chat_id == other.chat_id + and self.keyword == other.keyword + ) + + +class NewCustomFilters(BASE): + __tablename__ = "cust_filters_new" + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True, nullable=False) + text = Column(UnicodeText) + file_type = Column(Integer, nullable=False, default=1) + file_id = Column(UnicodeText, default=None) + + def __init__(self, chat_id, keyword, text, file_type, file_id): + self.chat_id = str(chat_id) # ensure string + self.keyword = keyword + self.text = text + self.file_type = file_type + self.file_id = file_id + + def __repr__(self): + return "" % self.chat_id + + def __eq__(self, other): + return bool( + isinstance(other, CustomFilters) + and self.chat_id == other.chat_id + and self.keyword == other.keyword + ) + + +class Buttons(BASE): + __tablename__ = "cust_filter_urls" + id = Column(Integer, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, keyword, name, url, same_line=False): + self.chat_id = str(chat_id) + self.keyword = keyword + self.name = name + self.url = url + self.same_line = same_line + + +CustomFilters.__table__.create(checkfirst=True) +Buttons.__table__.create(checkfirst=True) + +CUST_FILT_LOCK = threading.RLock() +BUTTON_LOCK = threading.RLock() +CHAT_FILTERS = {} + + +def get_all_filters(): + try: + return SESSION.query(CustomFilters).all() + finally: + SESSION.close() + + +def add_filter( + chat_id, + keyword, + reply, + is_sticker=False, + is_document=False, + is_image=False, + is_audio=False, + is_voice=False, + is_video=False, + buttons=None, +): + global CHAT_FILTERS + + if buttons is None: + buttons = [] + + with CUST_FILT_LOCK: + prev = SESSION.query(CustomFilters).get((str(chat_id), keyword)) + if prev: + with BUTTON_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + SESSION.delete(prev) + + filt = CustomFilters( + str(chat_id), + keyword, + reply, + is_sticker, + is_document, + is_image, + is_audio, + is_voice, + is_video, + bool(buttons), + ) + + if keyword not in CHAT_FILTERS.get(str(chat_id), []): + CHAT_FILTERS[str(chat_id)] = sorted( + CHAT_FILTERS.get(str(chat_id), []) + [keyword], + key=lambda x: (-len(x), x), + ) + + SESSION.add(filt) + SESSION.commit() + + for b_name, url, same_line in buttons: + add_note_button_to_db(chat_id, keyword, b_name, url, same_line) + + +def new_add_filter( + chat_id, keyword, reply_text, file_type, file_id, buttons, media_spoiler +): + global CHAT_FILTERS + + if buttons is None: + buttons = [] + + with CUST_FILT_LOCK: + prev = SESSION.query(CustomFilters).get((str(chat_id), keyword)) + if prev: + with BUTTON_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + SESSION.delete(prev) + + filt = CustomFilters( + str(chat_id), + keyword, + reply="there is should be a new reply", + is_sticker=False, + is_document=False, + is_image=False, + is_audio=False, + is_voice=False, + is_video=False, + has_buttons=bool(buttons), + reply_text=reply_text, + file_type=file_type.value, + file_id=file_id, + ) + + if keyword not in CHAT_FILTERS.get(str(chat_id), []): + CHAT_FILTERS[str(chat_id)] = sorted( + CHAT_FILTERS.get(str(chat_id), []) + [keyword], + key=lambda x: (-len(x), x), + ) + + SESSION.add(filt) + SESSION.commit() + + for b_name, url, same_line in buttons: + add_note_button_to_db(chat_id, keyword, b_name, url, same_line) + + +def remove_filter(chat_id, keyword): + global CHAT_FILTERS + with CUST_FILT_LOCK: + filt = SESSION.query(CustomFilters).get((str(chat_id), keyword)) + if filt: + if keyword in CHAT_FILTERS.get(str(chat_id), []): # Sanity check + CHAT_FILTERS.get(str(chat_id), []).remove(keyword) + + with BUTTON_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + + SESSION.delete(filt) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_chat_triggers(chat_id): + return CHAT_FILTERS.get(str(chat_id), set()) + + +def get_chat_filters(chat_id): + try: + return ( + SESSION.query(CustomFilters) + .filter(CustomFilters.chat_id == str(chat_id)) + .order_by(func.length(CustomFilters.keyword).desc()) + .order_by(CustomFilters.keyword.asc()) + .all() + ) + finally: + SESSION.close() + + +def get_filter(chat_id, keyword): + try: + return SESSION.query(CustomFilters).get((str(chat_id), keyword)) + finally: + SESSION.close() + + +def add_note_button_to_db(chat_id, keyword, b_name, url, same_line): + with BUTTON_LOCK: + button = Buttons(chat_id, keyword, b_name, url, same_line) + SESSION.add(button) + SESSION.commit() + + +def get_buttons(chat_id, keyword): + try: + return ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.keyword == keyword) + .order_by(Buttons.id) + .all() + ) + finally: + SESSION.close() + + +def num_filters(): + try: + return SESSION.query(CustomFilters).count() + finally: + SESSION.close() + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(CustomFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def __load_chat_filters(): + global CHAT_FILTERS + try: + chats = SESSION.query(CustomFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + CHAT_FILTERS[chat_id] = [] + + all_filters = SESSION.query(CustomFilters).all() + for x in all_filters: + CHAT_FILTERS[x.chat_id] += [x.keyword] + + CHAT_FILTERS = { + x: sorted(set(y), key=lambda i: (-len(i), i)) + for x, y in CHAT_FILTERS.items() + } + + finally: + SESSION.close() + + +# ONLY USE FOR MIGRATE OLD FILTERS TO NEW FILTERS +def __migrate_filters(): + try: + all_filters = SESSION.query(CustomFilters).distinct().all() + for x in all_filters: + if x.is_document: + file_type = Types.DOCUMENT + elif x.is_image: + file_type = Types.PHOTO + elif x.is_video: + file_type = Types.VIDEO + elif x.is_sticker: + file_type = Types.STICKER + elif x.is_audio: + file_type = Types.AUDIO + elif x.is_voice: + file_type = Types.VOICE + else: + file_type = Types.TEXT + + print(str(x.chat_id), x.keyword, x.reply, file_type.value) + if file_type == Types.TEXT: + filt = CustomFilters( + str(x.chat_id), x.keyword, x.reply, file_type.value, None + ) + else: + filt = CustomFilters( + str(x.chat_id), x.keyword, None, file_type.value, x.reply + ) + + SESSION.add(filt) + SESSION.commit() + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with CUST_FILT_LOCK: + chat_filters = ( + SESSION.query(CustomFilters) + .filter(CustomFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + try: + CHAT_FILTERS[str(new_chat_id)] = CHAT_FILTERS[str(old_chat_id)] + except KeyError: + pass + del CHAT_FILTERS[str(old_chat_id)] + + with BUTTON_LOCK: + chat_buttons = ( + SESSION.query(Buttons).filter(Buttons.chat_id == str(old_chat_id)).all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_chat_filters() diff --git a/Database/sql/disable_sql.py b/Database/sql/disable_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..f67aba0b15ff623d6b3901725f9332b230398ae8 --- /dev/null +++ b/Database/sql/disable_sql.py @@ -0,0 +1,129 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Column, String, UnicodeText, distinct, func + +from Database.sql import BASE, SESSION + + +class Disable(BASE): + __tablename__ = "disabled_commands" + chat_id = Column(String(14), primary_key=True) + command = Column(UnicodeText, primary_key=True) + + def __init__(self, chat_id, command): + self.chat_id = chat_id + self.command = command + + def __repr__(self): + return "ᴅɪsᴀʙʟᴇᴅ ᴄᴍᴅ {} in {}".format(self.command, self.chat_id) + + +Disable.__table__.create(checkfirst=True) +DISABLE_INSERTION_LOCK = threading.RLock() + +DISABLED = {} + + +def disable_command(chat_id, disable): + with DISABLE_INSERTION_LOCK: + disabled = SESSION.query(Disable).get((str(chat_id), disable)) + + if not disabled: + DISABLED.setdefault(str(chat_id), set()).add(disable) + + disabled = Disable(str(chat_id), disable) + SESSION.add(disabled) + SESSION.commit() + return True + + SESSION.close() + return False + + +def enable_command(chat_id, enable): + with DISABLE_INSERTION_LOCK: + disabled = SESSION.query(Disable).get((str(chat_id), enable)) + + if disabled: + if enable in DISABLED.get(str(chat_id)): # sanity check + DISABLED.setdefault(str(chat_id), set()).remove(enable) + + SESSION.delete(disabled) + SESSION.commit() + return True + + SESSION.close() + return False + + +def is_command_disabled(chat_id, cmd): + return str(cmd).lower() in DISABLED.get(str(chat_id), set()) + + +def get_all_disabled(chat_id): + return DISABLED.get(str(chat_id), set()) + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(Disable.chat_id))).scalar() + finally: + SESSION.close() + + +def num_disabled(): + try: + return SESSION.query(Disable).count() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with DISABLE_INSERTION_LOCK: + chats = SESSION.query(Disable).filter(Disable.chat_id == str(old_chat_id)).all() + for chat in chats: + chat.chat_id = str(new_chat_id) + SESSION.add(chat) + + if str(old_chat_id) in DISABLED: + DISABLED[str(new_chat_id)] = DISABLED.get(str(old_chat_id), set()) + + SESSION.commit() + + +def __load_disabled_commands(): + global DISABLED + try: + all_chats = SESSION.query(Disable).all() + for chat in all_chats: + DISABLED.setdefault(chat.chat_id, set()).add(chat.command) + + finally: + SESSION.close() + + +__load_disabled_commands() diff --git a/Database/sql/feds_sql.py b/Database/sql/feds_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..3d73ddb331d7864344e3402893338ff915c77a04 --- /dev/null +++ b/Database/sql/feds_sql.py @@ -0,0 +1,931 @@ +import ast +import threading + +from sqlalchemy import BigInteger, Boolean, Column, Integer, String, UnicodeText +from telegram.error import BadRequest, Forbidden + +from Database.sql import BASE, SESSION +from Mikobot import dispatcher + + +class Federations(BASE): + __tablename__ = "feds" + owner_id = Column(String(14)) + fed_name = Column(UnicodeText) + fed_id = Column(UnicodeText, primary_key=True) + fed_rules = Column(UnicodeText) + fed_log = Column(UnicodeText) + fed_users = Column(UnicodeText) + + def __init__(self, owner_id, fed_name, fed_id, fed_rules, fed_log, fed_users): + self.owner_id = owner_id + self.fed_name = fed_name + self.fed_id = fed_id + self.fed_rules = fed_rules + self.fed_log = fed_log + self.fed_users = fed_users + + +class ChatF(BASE): + __tablename__ = "chat_feds" + chat_id = Column(String(14), primary_key=True) + chat_name = Column(UnicodeText) + fed_id = Column(UnicodeText) + + def __init__(self, chat_id, chat_name, fed_id): + self.chat_id = chat_id + self.chat_name = chat_name + self.fed_id = fed_id + + +class BansF(BASE): + __tablename__ = "bans_feds" + fed_id = Column(UnicodeText, primary_key=True) + user_id = Column(String(14), primary_key=True) + first_name = Column(UnicodeText, nullable=False) + last_name = Column(UnicodeText) + user_name = Column(UnicodeText) + reason = Column(UnicodeText, default="") + time = Column(Integer, default=0) + + def __init__(self, fed_id, user_id, first_name, last_name, user_name, reason, time): + self.fed_id = fed_id + self.user_id = user_id + self.first_name = first_name + self.last_name = last_name + self.user_name = user_name + self.reason = reason + self.time = time + + +class FedsUserSettings(BASE): + __tablename__ = "feds_settings" + user_id = Column(BigInteger, primary_key=True) + should_report = Column(Boolean, default=True) + + def __init__(self, user_id): + self.user_id = user_id + + def __repr__(self): + return "".format(self.user_id) + + +class FedSubs(BASE): + __tablename__ = "feds_subs" + fed_id = Column(UnicodeText, primary_key=True) + fed_subs = Column(UnicodeText, primary_key=True, nullable=False) + + def __init__(self, fed_id, fed_subs): + self.fed_id = fed_id + self.fed_subs = fed_subs + + def __repr__(self): + return "".format(self.fed_id, self.fed_subs) + + +# Dropping db +# Federations.__table__.drop() +# ChatF.__table__.drop() +# BansF.__table__.drop() +# FedSubs.__table__.drop() + +Federations.__table__.create(checkfirst=True) +ChatF.__table__.create(checkfirst=True) +BansF.__table__.create(checkfirst=True) +FedsUserSettings.__table__.create(checkfirst=True) +FedSubs.__table__.create(checkfirst=True) + +FEDS_LOCK = threading.RLock() +CHAT_FEDS_LOCK = threading.RLock() +FEDS_SETTINGS_LOCK = threading.RLock() +FEDS_SUBSCRIBER_LOCK = threading.RLock() + +FEDERATION_BYNAME = {} +FEDERATION_BYOWNER = {} +FEDERATION_BYFEDID = {} + +FEDERATION_CHATS = {} +FEDERATION_CHATS_BYID = {} + +FEDERATION_BANNED_FULL = {} +FEDERATION_BANNED_USERID = {} + +FEDERATION_NOTIFICATION = {} +FEDS_SUBSCRIBER = {} +MYFEDS_SUBSCRIBER = {} + + +def get_fed_info(fed_id): + get = FEDERATION_BYFEDID.get(str(fed_id)) + if get is None: + return False + return get + + +def get_fed_id(chat_id): + get = FEDERATION_CHATS.get(str(chat_id)) + if get is None: + return False + else: + return get["fid"] + + +def get_fed_name(chat_id): + get = FEDERATION_CHATS.get(str(chat_id)) + if get is None: + return False + else: + return get["chat_name"] + + +def get_user_fban(fed_id, user_id): + if not FEDERATION_BANNED_FULL.get(fed_id): + return False, False, False + user_info = FEDERATION_BANNED_FULL[fed_id].get(user_id) + if not user_info: + return None, None, None + return user_info["first_name"], user_info["reason"], user_info["time"] + + +def get_user_admin_fed_name(user_id): + user_feds = [] + for f in FEDERATION_BYFEDID: + if int(user_id) in ast.literal_eval( + ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["members"] + ): + user_feds.append(FEDERATION_BYFEDID[f]["fname"]) + return user_feds + + +def get_user_owner_fed_name(user_id): + user_feds = [] + for f in FEDERATION_BYFEDID: + if int(user_id) == int( + ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["owner"] + ): + user_feds.append(FEDERATION_BYFEDID[f]["fname"]) + return user_feds + + +def get_user_admin_fed_full(user_id): + user_feds = [] + for f in FEDERATION_BYFEDID: + if int(user_id) in ast.literal_eval( + ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["members"] + ): + user_feds.append({"fed_id": f, "fed": FEDERATION_BYFEDID[f]}) + return user_feds + + +def get_user_owner_fed_full(user_id): + user_feds = [] + for f in FEDERATION_BYFEDID: + if int(user_id) == int( + ast.literal_eval(FEDERATION_BYFEDID[f]["fusers"])["owner"] + ): + user_feds.append({"fed_id": f, "fed": FEDERATION_BYFEDID[f]}) + return user_feds + + +def get_user_fbanlist(user_id): + banlist = FEDERATION_BANNED_FULL + user_name = "" + fedname = [] + for x in banlist: + if banlist[x].get(user_id): + if user_name == "": + user_name = banlist[x][user_id].get("first_name") + fedname.append([x, banlist[x][user_id].get("reason")]) + return user_name, fedname + + +def new_fed(owner_id, fed_name, fed_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + "Rules is not set in this federation.", + None, + str({"owner": str(owner_id), "members": "[]"}), + ) + SESSION.add(fed) + SESSION.commit() + FEDERATION_BYOWNER[str(owner_id)] = { + "fid": str(fed_id), + "fname": fed_name, + "frules": "Rules is not set in this federation.", + "flog": None, + "fusers": str({"owner": str(owner_id), "members": "[]"}), + } + FEDERATION_BYFEDID[str(fed_id)] = { + "owner": str(owner_id), + "fname": fed_name, + "frules": "Rules is not set in this federation.", + "flog": None, + "fusers": str({"owner": str(owner_id), "members": "[]"}), + } + FEDERATION_BYNAME[fed_name] = { + "fid": str(fed_id), + "owner": str(owner_id), + "frules": "Rules is not set in this federation.", + "flog": None, + "fusers": str({"owner": str(owner_id), "members": "[]"}), + } + return fed + + +def del_fed(fed_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME, FEDERATION_CHATS, FEDERATION_CHATS_BYID, FEDERATION_BANNED_USERID, FEDERATION_BANNED_FULL + getcache = FEDERATION_BYFEDID.get(fed_id) + if getcache is None: + return False + # Variables + getfed = FEDERATION_BYFEDID.get(fed_id) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + # Delete from cache + FEDERATION_BYOWNER.pop(owner_id) + FEDERATION_BYFEDID.pop(fed_id) + FEDERATION_BYNAME.pop(fed_name) + if FEDERATION_CHATS_BYID.get(fed_id): + for x in FEDERATION_CHATS_BYID[fed_id]: + delchats = SESSION.query(ChatF).get(str(x)) + if delchats: + SESSION.delete(delchats) + SESSION.commit() + FEDERATION_CHATS.pop(x) + FEDERATION_CHATS_BYID.pop(fed_id) + # Delete fedban users + getall = FEDERATION_BANNED_USERID.get(fed_id) + if getall: + for x in getall: + banlist = SESSION.query(BansF).get((fed_id, str(x))) + if banlist: + SESSION.delete(banlist) + SESSION.commit() + if FEDERATION_BANNED_USERID.get(fed_id): + FEDERATION_BANNED_USERID.pop(fed_id) + if FEDERATION_BANNED_FULL.get(fed_id): + FEDERATION_BANNED_FULL.pop(fed_id) + # Delete fedsubs + getall = MYFEDS_SUBSCRIBER.get(fed_id) + if getall: + for x in getall: + getsubs = SESSION.query(FedSubs).get((fed_id, str(x))) + if getsubs: + SESSION.delete(getsubs) + SESSION.commit() + if FEDS_SUBSCRIBER.get(fed_id): + FEDS_SUBSCRIBER.pop(fed_id) + if MYFEDS_SUBSCRIBER.get(fed_id): + MYFEDS_SUBSCRIBER.pop(fed_id) + # Delete from database + curr = SESSION.query(Federations).get(fed_id) + if curr: + SESSION.delete(curr) + SESSION.commit() + return True + + +def rename_fed(fed_id, owner_id, newname): + with FEDS_LOCK: + global FEDERATION_BYFEDID, FEDERATION_BYOWNER, FEDERATION_BYNAME + fed = SESSION.query(Federations).get(fed_id) + if not fed: + return False + fed.fed_name = newname + SESSION.commit() + + # Update the dicts + oldname = FEDERATION_BYFEDID[str(fed_id)]["fname"] + tempdata = FEDERATION_BYNAME[oldname] + FEDERATION_BYNAME.pop(oldname) + + FEDERATION_BYOWNER[str(owner_id)]["fname"] = newname + FEDERATION_BYFEDID[str(fed_id)]["fname"] = newname + FEDERATION_BYNAME[newname] = tempdata + return True + + +def chat_join_fed(fed_id, chat_name, chat_id): + with FEDS_LOCK: + global FEDERATION_CHATS, FEDERATION_CHATS_BYID + r = ChatF(chat_id, chat_name, fed_id) + SESSION.add(r) + FEDERATION_CHATS[str(chat_id)] = {"chat_name": chat_name, "fid": fed_id} + checkid = FEDERATION_CHATS_BYID.get(fed_id) + if checkid is None: + FEDERATION_CHATS_BYID[fed_id] = [] + FEDERATION_CHATS_BYID[fed_id].append(str(chat_id)) + SESSION.commit() + return r + + +def search_fed_by_name(fed_name): + allfed = FEDERATION_BYNAME.get(fed_name) + if allfed is None: + return False + return allfed + + +def search_user_in_fed(fed_id, user_id): + getfed = FEDERATION_BYFEDID.get(fed_id) + if getfed is None: + return False + getfed = ast.literal_eval(getfed["fusers"])["members"] + if user_id in ast.literal_eval(getfed): + return True + else: + return False + + +def user_demote_fed(fed_id, user_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_rules = getfed["frules"] + fed_log = getfed["flog"] + # Temp set + try: + members = ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + except ValueError: + return False + members.remove(user_id) + # Set user + FEDERATION_BYOWNER[str(owner_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYFEDID[str(fed_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYNAME[fed_name]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str({"owner": str(owner_id), "members": str(members)}), + ) + SESSION.merge(fed) + SESSION.commit() + return True + + curr = SESSION.query(UserF).all() + result = False + for r in curr: + if int(r.user_id) == int(user_id): + if r.fed_id == fed_id: + SESSION.delete(r) + SESSION.commit() + result = True + + SESSION.close() + return result + + +def user_join_fed(fed_id, user_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_rules = getfed["frules"] + fed_log = getfed["flog"] + # Temp set + members = ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + members.append(user_id) + # Set user + FEDERATION_BYOWNER[str(owner_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYFEDID[str(fed_id)]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + FEDERATION_BYNAME[fed_name]["fusers"] = str( + {"owner": str(owner_id), "members": str(members)}, + ) + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str({"owner": str(owner_id), "members": str(members)}), + ) + SESSION.merge(fed) + SESSION.commit() + __load_all_feds_chats() + return True + + +def chat_leave_fed(chat_id): + with FEDS_LOCK: + global FEDERATION_CHATS, FEDERATION_CHATS_BYID + # Set variables + fed_info = FEDERATION_CHATS.get(str(chat_id)) + if fed_info is None: + return False + fed_id = fed_info["fid"] + # Delete from cache + FEDERATION_CHATS.pop(str(chat_id)) + FEDERATION_CHATS_BYID[str(fed_id)].remove(str(chat_id)) + # Delete from db + curr = SESSION.query(ChatF).all() + for U in curr: + if int(U.chat_id) == int(chat_id): + SESSION.delete(U) + SESSION.commit() + return True + + +def all_fed_chats(fed_id): + with FEDS_LOCK: + getfed = FEDERATION_CHATS_BYID.get(fed_id) + if getfed is None: + return [] + else: + return getfed + + +def all_fed_users(fed_id): + with FEDS_LOCK: + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + if getfed is None: + return False + fed_owner = ast.literal_eval(ast.literal_eval(getfed["fusers"])["owner"]) + fed_admins = ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + fed_admins.append(fed_owner) + return fed_admins + + +def all_fed_members(fed_id): + with FEDS_LOCK: + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + fed_admins = ast.literal_eval(ast.literal_eval(getfed["fusers"])["members"]) + return fed_admins + + +def set_frules(fed_id, rules): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_members = getfed["fusers"] + fed_rules = str(rules) + fed_log = getfed["flog"] + # Set user + FEDERATION_BYOWNER[str(owner_id)]["frules"] = fed_rules + FEDERATION_BYFEDID[str(fed_id)]["frules"] = fed_rules + FEDERATION_BYNAME[fed_name]["frules"] = fed_rules + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str(fed_members), + ) + SESSION.merge(fed) + SESSION.commit() + return True + + +def get_frules(fed_id): + with FEDS_LOCK: + rules = FEDERATION_BYFEDID[str(fed_id)]["frules"] + return rules + + +def fban_user(fed_id, user_id, first_name, last_name, user_name, reason, time): + with FEDS_LOCK: + r = SESSION.query(BansF).all() + for I in r: + if I.fed_id == fed_id: + if int(I.user_id) == int(user_id): + SESSION.delete(I) + + r = BansF( + str(fed_id), + str(user_id), + first_name, + last_name, + user_name, + reason, + time, + ) + + SESSION.add(r) + try: + SESSION.commit() + except: + SESSION.rollback() + return False + finally: + SESSION.commit() + __load_all_feds_banned() + return r + + +def multi_fban_user( + multi_fed_id, + multi_user_id, + multi_first_name, + multi_last_name, + multi_user_name, + multi_reason, +): + if True: # with FEDS_LOCK: + counter = 0 + time = 0 + for x in range(len(multi_fed_id)): + fed_id = multi_fed_id[x] + user_id = multi_user_id[x] + first_name = multi_first_name[x] + last_name = multi_last_name[x] + user_name = multi_user_name[x] + reason = multi_reason[x] + r = SESSION.query(BansF).all() + for I in r: + if I.fed_id == fed_id: + if int(I.user_id) == int(user_id): + SESSION.delete(I) + + r = BansF( + str(fed_id), + str(user_id), + first_name, + last_name, + user_name, + reason, + time, + ) + + SESSION.add(r) + counter += 1 + try: + SESSION.commit() + except: + SESSION.rollback() + return False + finally: + SESSION.commit() + __load_all_feds_banned() + return counter + + +def un_fban_user(fed_id, user_id): + with FEDS_LOCK: + r = SESSION.query(BansF).all() + for I in r: + if I.fed_id == fed_id: + if int(I.user_id) == int(user_id): + SESSION.delete(I) + try: + SESSION.commit() + except: + SESSION.rollback() + return False + finally: + SESSION.commit() + __load_all_feds_banned() + return I + + +def get_fban_user(fed_id, user_id): + list_fbanned = FEDERATION_BANNED_USERID.get(fed_id) + if list_fbanned is None: + FEDERATION_BANNED_USERID[fed_id] = [] + if user_id in FEDERATION_BANNED_USERID[fed_id]: + r = SESSION.query(BansF).all() + reason = None + for I in r: + if I.fed_id == fed_id: + if int(I.user_id) == int(user_id): + reason = I.reason + time = I.time + return True, reason, time + else: + return False, None, None + + +def get_all_fban_users(fed_id): + list_fbanned = FEDERATION_BANNED_USERID.get(fed_id) + if list_fbanned is None: + FEDERATION_BANNED_USERID[fed_id] = [] + return FEDERATION_BANNED_USERID[fed_id] + + +def get_all_fban_users_target(fed_id, user_id): + list_fbanned = FEDERATION_BANNED_FULL.get(fed_id) + if list_fbanned is None: + FEDERATION_BANNED_FULL[fed_id] = [] + return False + getuser = list_fbanned[str(user_id)] + return getuser + + +def get_all_fban_users_global(): + list_fbanned = FEDERATION_BANNED_USERID + total = [] + for x in list(FEDERATION_BANNED_USERID): + for y in FEDERATION_BANNED_USERID[x]: + total.append(y) + return total + + +def get_all_feds_users_global(): + list_fed = FEDERATION_BYFEDID + total = [] + for x in list(FEDERATION_BYFEDID): + total.append(FEDERATION_BYFEDID[x]) + return total + + +def search_fed_by_id(fed_id): + get = FEDERATION_BYFEDID.get(fed_id) + if get is None: + return False + else: + return get + result = False + for Q in curr: + if Q.fed_id == fed_id: + result = Q.fed_id + + return result + + +def user_feds_report(user_id: int) -> bool: + user_setting = FEDERATION_NOTIFICATION.get(str(user_id)) + if user_setting is None: + user_setting = True + return user_setting + + +def set_feds_setting(user_id: int, setting: bool): + with FEDS_SETTINGS_LOCK: + global FEDERATION_NOTIFICATION + user_setting = SESSION.query(FedsUserSettings).get(user_id) + if not user_setting: + user_setting = FedsUserSettings(user_id) + + user_setting.should_report = setting + FEDERATION_NOTIFICATION[str(user_id)] = setting + SESSION.add(user_setting) + SESSION.commit() + + +async def get_fed_log(fed_id): + fed_setting = FEDERATION_BYFEDID.get(str(fed_id)) + if fed_setting is None: + fed_setting = False + return fed_setting + if fed_setting.get("flog") is None: + return False + elif fed_setting.get("flog"): + try: + await dispatcher.bot.get_chat(fed_setting.get("flog")) + except BadRequest: + set_fed_log(fed_id, None) + return False + except Forbidden: + set_fed_log(fed_id, None) + return False + return fed_setting.get("flog") + else: + return False + + +def set_fed_log(fed_id, chat_id): + with FEDS_LOCK: + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + # Variables + getfed = FEDERATION_BYFEDID.get(str(fed_id)) + owner_id = getfed["owner"] + fed_name = getfed["fname"] + fed_members = getfed["fusers"] + fed_rules = getfed["frules"] + fed_log = str(chat_id) + # Set user + FEDERATION_BYOWNER[str(owner_id)]["flog"] = fed_log + FEDERATION_BYFEDID[str(fed_id)]["flog"] = fed_log + FEDERATION_BYNAME[fed_name]["flog"] = fed_log + # Set on database + fed = Federations( + str(owner_id), + fed_name, + str(fed_id), + fed_rules, + fed_log, + str(fed_members), + ) + SESSION.merge(fed) + SESSION.commit() + return True + + +def subs_fed(fed_id, my_fed): + check = get_spec_subs(fed_id, my_fed) + if check: + return False + with FEDS_SUBSCRIBER_LOCK: + subsfed = FedSubs(fed_id, my_fed) + + SESSION.merge(subsfed) # merge to avoid duplicate key issues + SESSION.commit() + global FEDS_SUBSCRIBER, MYFEDS_SUBSCRIBER + # Temporary Data For Subbed Feds + if FEDS_SUBSCRIBER.get(fed_id, set()) == set(): + FEDS_SUBSCRIBER[fed_id] = {my_fed} + else: + FEDS_SUBSCRIBER.get(fed_id, set()).add(my_fed) + # Temporary data for Fed Subs + if MYFEDS_SUBSCRIBER.get(my_fed, set()) == set(): + MYFEDS_SUBSCRIBER[my_fed] = {fed_id} + else: + MYFEDS_SUBSCRIBER.get(my_fed, set()).add(fed_id) + return True + + +def unsubs_fed(fed_id, my_fed): + with FEDS_SUBSCRIBER_LOCK: + getsubs = SESSION.query(FedSubs).get((fed_id, my_fed)) + if getsubs: + if my_fed in FEDS_SUBSCRIBER.get(fed_id, set()): # sanity check + FEDS_SUBSCRIBER.get(fed_id, set()).remove(my_fed) + if fed_id in MYFEDS_SUBSCRIBER.get(my_fed, set()): # sanity check + MYFEDS_SUBSCRIBER.get(my_fed, set()).remove(fed_id) + + SESSION.delete(getsubs) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_all_subs(fed_id): + return FEDS_SUBSCRIBER.get(fed_id, set()) + + +def get_spec_subs(fed_id, fed_target): + if FEDS_SUBSCRIBER.get(fed_id, set()) == set(): + return {} + else: + return FEDS_SUBSCRIBER.get(fed_id, fed_target) + + +def get_mysubs(my_fed): + return list(MYFEDS_SUBSCRIBER.get(my_fed)) + + +def get_subscriber(fed_id): + return FEDS_SUBSCRIBER.get(fed_id, set()) + + +def __load_all_feds(): + global FEDERATION_BYOWNER, FEDERATION_BYFEDID, FEDERATION_BYNAME + try: + feds = SESSION.query(Federations).all() + for x in feds: # remove tuple by ( ,) + # Fed by Owner + check = FEDERATION_BYOWNER.get(x.owner_id) + if check is None: + FEDERATION_BYOWNER[x.owner_id] = [] + FEDERATION_BYOWNER[str(x.owner_id)] = { + "fid": str(x.fed_id), + "fname": x.fed_name, + "frules": x.fed_rules, + "flog": x.fed_log, + "fusers": str(x.fed_users), + } + # Fed By FedId + check = FEDERATION_BYFEDID.get(x.fed_id) + if check is None: + FEDERATION_BYFEDID[x.fed_id] = [] + FEDERATION_BYFEDID[str(x.fed_id)] = { + "owner": str(x.owner_id), + "fname": x.fed_name, + "frules": x.fed_rules, + "flog": x.fed_log, + "fusers": str(x.fed_users), + } + # Fed By Name + check = FEDERATION_BYNAME.get(x.fed_name) + if check is None: + FEDERATION_BYNAME[x.fed_name] = [] + FEDERATION_BYNAME[x.fed_name] = { + "fid": str(x.fed_id), + "owner": str(x.owner_id), + "frules": x.fed_rules, + "flog": x.fed_log, + "fusers": str(x.fed_users), + } + finally: + SESSION.close() + + +def __load_all_feds_chats(): + global FEDERATION_CHATS, FEDERATION_CHATS_BYID + try: + qall = SESSION.query(ChatF).all() + FEDERATION_CHATS = {} + FEDERATION_CHATS_BYID = {} + for x in qall: + # Federation Chats + check = FEDERATION_CHATS.get(x.chat_id) + if check is None: + FEDERATION_CHATS[x.chat_id] = {} + FEDERATION_CHATS[x.chat_id] = {"chat_name": x.chat_name, "fid": x.fed_id} + # Federation Chats By ID + check = FEDERATION_CHATS_BYID.get(x.fed_id) + if check is None: + FEDERATION_CHATS_BYID[x.fed_id] = [] + FEDERATION_CHATS_BYID[x.fed_id].append(x.chat_id) + finally: + SESSION.close() + + +def __load_all_feds_banned(): + global FEDERATION_BANNED_USERID, FEDERATION_BANNED_FULL + try: + FEDERATION_BANNED_USERID = {} + FEDERATION_BANNED_FULL = {} + qall = SESSION.query(BansF).all() + for x in qall: + check = FEDERATION_BANNED_USERID.get(x.fed_id) + if check is None: + FEDERATION_BANNED_USERID[x.fed_id] = [] + if int(x.user_id) not in FEDERATION_BANNED_USERID[x.fed_id]: + FEDERATION_BANNED_USERID[x.fed_id].append(int(x.user_id)) + check = FEDERATION_BANNED_FULL.get(x.fed_id) + if check is None: + FEDERATION_BANNED_FULL[x.fed_id] = {} + FEDERATION_BANNED_FULL[x.fed_id][x.user_id] = { + "first_name": x.first_name, + "last_name": x.last_name, + "user_name": x.user_name, + "reason": x.reason, + "time": x.time, + } + finally: + SESSION.close() + + +def __load_all_feds_settings(): + global FEDERATION_NOTIFICATION + try: + getuser = SESSION.query(FedsUserSettings).all() + for x in getuser: + FEDERATION_NOTIFICATION[str(x.user_id)] = x.should_report + finally: + SESSION.close() + + +def __load_feds_subscriber(): + global FEDS_SUBSCRIBER + global MYFEDS_SUBSCRIBER + try: + feds = SESSION.query(FedSubs.fed_id).distinct().all() + for (fed_id,) in feds: # remove tuple by ( ,) + FEDS_SUBSCRIBER[fed_id] = [] + MYFEDS_SUBSCRIBER[fed_id] = [] + + all_fedsubs = SESSION.query(FedSubs).all() + for x in all_fedsubs: + FEDS_SUBSCRIBER[x.fed_id] += [x.fed_subs] + try: + MYFEDS_SUBSCRIBER[x.fed_subs] += [x.fed_id] + except KeyError: + getsubs = SESSION.query(FedSubs).get((x.fed_id, x.fed_subs)) + if getsubs: + SESSION.delete(getsubs) + SESSION.commit() + + FEDS_SUBSCRIBER = {x: set(y) for x, y in FEDS_SUBSCRIBER.items()} + MYFEDS_SUBSCRIBER = {x: set(y) for x, y in MYFEDS_SUBSCRIBER.items()} + + finally: + SESSION.close() + + +__load_all_feds() +__load_all_feds_chats() +__load_all_feds_banned() +__load_all_feds_settings() +__load_feds_subscriber() diff --git a/Database/sql/fontsql.py b/Database/sql/fontsql.py new file mode 100644 index 0000000000000000000000000000000000000000..c4e177466540d1b6dd21bb179e31f471c407d9f5 --- /dev/null +++ b/Database/sql/fontsql.py @@ -0,0 +1,2361 @@ +class Fonts: + def typewriter(text): + style = { + "a": "𝚊", + "b": "𝚋", + "c": "𝚌", + "d": "𝚍", + "e": "𝚎", + "f": "𝚏", + "g": "𝚐", + "h": "𝚑", + "i": "𝚒", + "j": "𝚓", + "k": "𝚔", + "l": "𝚕", + "m": "𝚖", + "n": "𝚗", + "o": "𝚘", + "p": "𝚙", + "q": "𝚚", + "r": "𝚛", + "s": "𝚜", + "t": "𝚝", + "u": "𝚞", + "v": "𝚟", + "w": "𝚠", + "x": "𝚡", + "y": "𝚢", + "z": "𝚣", + "A": "𝙰", + "B": "𝙱", + "C": "𝙲", + "D": "𝙳", + "E": "𝙴", + "F": "𝙵", + "G": "𝙶", + "H": "𝙷", + "I": "𝙸", + "J": "𝙹", + "K": "𝙺", + "L": "𝙻", + "M": "𝙼", + "N": "𝙽", + "O": "𝙾", + "P": "𝙿", + "Q": "𝚀", + "R": "𝚁", + "S": "𝚂", + "T": "𝚃", + "U": "𝚄", + "V": "𝚅", + "W": "𝚆", + "X": "𝚇", + "Y": "𝚈", + "Z": "𝚉", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def outline(text): + style = { + "a": "𝕒", + "b": "𝕓", + "c": "𝕔", + "d": "𝕕", + "e": "𝕖", + "f": "𝕗", + "g": "𝕘", + "h": "𝕙", + "i": "𝕚", + "j": "𝕛", + "k": "𝕜", + "l": "𝕝", + "m": "𝕞", + "n": "𝕟", + "o": "𝕠", + "p": "𝕡", + "q": "𝕢", + "r": "𝕣", + "s": "𝕤", + "t": "𝕥", + "u": "𝕦", + "v": "𝕧", + "w": "𝕨", + "x": "𝕩", + "y": "𝕪", + "z": "𝕫", + "A": "𝔸", + "B": "𝔹", + "C": "ℂ", + "D": "𝔻", + "E": "𝔼", + "F": "𝔽", + "G": "𝔾", + "H": "ℍ", + "I": "𝕀", + "J": "𝕁", + "K": "𝕂", + "L": "𝕃", + "M": "𝕄", + "N": "ℕ", + "O": "𝕆", + "P": "ℙ", + "Q": "ℚ", + "R": "ℝ", + "S": "𝕊", + "T": "𝕋", + "U": "𝕌", + "V": "𝕍", + "W": "𝕎", + "X": "𝕏", + "Y": "𝕐", + "Z": "ℤ", + "0": "𝟘", + "1": "𝟙", + "2": "𝟚", + "3": "𝟛", + "4": "𝟜", + "5": "𝟝", + "6": "𝟞", + "7": "𝟟", + "8": "𝟠", + "9": "𝟡", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def serief(text): + style = { + "a": "𝐚", + "b": "𝐛", + "c": "𝐜", + "d": "𝐝", + "e": "𝐞", + "f": "𝐟", + "g": "𝐠", + "h": "𝐡", + "i": "𝐢", + "j": "𝐣", + "k": "𝐤", + "l": "𝐥", + "m": "𝐦", + "n": "𝐧", + "o": "𝐨", + "p": "𝐩", + "q": "𝐪", + "r": "𝐫", + "s": "𝐬", + "t": "𝐭", + "u": "𝐮", + "v": "𝐯", + "w": "𝐰", + "x": "𝐱", + "y": "𝐲", + "z": "𝐳", + "A": "𝐀", + "B": "𝐁", + "C": "𝐂", + "D": "𝐃", + "E": "𝐄", + "F": "𝐅", + "G": "𝐆", + "H": "𝐇", + "I": "𝐈", + "J": "𝐉", + "K": "𝐊", + "L": "𝐋", + "M": "𝐌", + "N": "𝐍", + "O": "𝐎", + "P": "𝐏", + "Q": "𝐐", + "R": "𝐑", + "S": "𝐒", + "T": "𝐓", + "U": "𝐔", + "V": "𝐕", + "W": "𝐖", + "X": "𝐗", + "Y": "𝐘", + "Z": "𝐙", + "0": "𝟎", + "1": "𝟏", + "2": "𝟐", + "3": "𝟑", + "4": "𝟒", + "5": "𝟓", + "6": "𝟔", + "7": "𝟕", + "8": "𝟖", + "9": "𝟗", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def bold_cool(text): + style = { + "a": "𝒂", + "b": "𝒃", + "c": "𝒄", + "d": "𝒅", + "e": "𝒆", + "f": "𝒇", + "g": "𝒈", + "h": "𝒉", + "i": "𝒊", + "j": "𝒋", + "k": "𝒌", + "l": "𝒍", + "m": "𝒎", + "n": "𝒏", + "o": "𝒐", + "p": "𝒑", + "q": "𝒒", + "r": "𝒓", + "s": "𝒔", + "t": "𝒕", + "u": "𝒖", + "v": "𝒗", + "w": "𝒘", + "x": "𝒙", + "y": "𝒚", + "z": "𝒛", + "A": "𝑨", + "B": "𝑩", + "C": "𝑪", + "D": "𝑫", + "E": "𝑬", + "F": "𝑭", + "G": "𝑮", + "H": "𝑯", + "I": "𝑰", + "J": "𝑱", + "K": "𝑲", + "L": "𝑳", + "M": "𝑴", + "N": "𝑵", + "O": "𝑶", + "P": "𝑷", + "Q": "𝑸", + "R": "𝑹", + "S": "𝑺", + "T": "𝑻", + "U": "𝑼", + "V": "𝑽", + "W": "𝑾", + "X": "𝑿", + "Y": "𝒀", + "Z": "𝒁", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def cool(text): + style = { + "a": "𝑎", + "b": "𝑏", + "c": "𝑐", + "d": "𝑑", + "e": "𝑒", + "f": "𝑓", + "g": "𝑔", + "h": "ℎ", + "i": "𝑖", + "j": "𝑗", + "k": "𝑘", + "l": "𝑙", + "m": "𝑚", + "n": "𝑛", + "o": "𝑜", + "p": "𝑝", + "q": "𝑞", + "r": "𝑟", + "s": "𝑠", + "t": "𝑡", + "u": "𝑢", + "v": "𝑣", + "w": "𝑤", + "x": "𝑥", + "y": "𝑦", + "z": "𝑧", + "A": "𝐴", + "B": "𝐵", + "C": "𝐶", + "D": "𝐷", + "E": "𝐸", + "F": "𝐹", + "G": "𝐺", + "H": "𝐻", + "I": "𝐼", + "J": "𝐽", + "K": "𝐾", + "L": "𝐿", + "M": "𝑀", + "N": "𝑁", + "O": "𝑂", + "P": "𝑃", + "Q": "𝑄", + "R": "𝑅", + "S": "𝑆", + "T": "𝑇", + "U": "𝑈", + "V": "𝑉", + "W": "𝑊", + "X": "𝑋", + "Y": "𝑌", + "Z": "𝑍", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def smallcap(text): + style = { + "a": "ᴀ", + "b": "ʙ", + "c": "ᴄ", + "d": "ᴅ", + "e": "ᴇ", + "f": "ғ", + "g": "ɢ", + "h": "ʜ", + "i": "ɪ", + "j": "J", + "k": "ᴋ", + "l": "ʟ", + "m": "ᴍ", + "n": "ɴ", + "o": "ᴏ", + "p": "ᴘ", + "q": "ǫ", + "r": "ʀ", + "s": "s", + "t": "ᴛ", + "u": "ᴜ", + "v": "ᴠ", + "w": "ᴡ", + "x": "x", + "y": "ʏ", + "z": "ᴢ", + "A": "A", + "B": "B", + "C": "C", + "D": "D", + "E": "E", + "F": "F", + "G": "G", + "H": "H", + "I": "I", + "J": "J", + "K": "K", + "L": "L", + "M": "M", + "N": "N", + "O": "O", + "P": "P", + "Q": "Q", + "R": "R", + "S": "S", + "T": "T", + "U": "U", + "V": "V", + "W": "W", + "X": "X", + "Y": "Y", + "Z": "Z", + "0": "𝟶", + "1": "𝟷", + "2": "𝟸", + "3": "𝟹", + "4": "𝟺", + "5": "𝟻", + "6": "𝟼", + "7": "𝟽", + "8": "𝟾", + "9": "𝟿", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def script(text): + style = { + "a": "𝒶", + "b": "𝒷", + "c": "𝒸", + "d": "𝒹", + "e": "ℯ", + "f": "𝒻", + "g": "ℊ", + "h": "𝒽", + "i": "𝒾", + "j": "𝒿", + "k": "𝓀", + "l": "𝓁", + "m": "𝓂", + "n": "𝓃", + "o": "ℴ", + "p": "𝓅", + "q": "𝓆", + "r": "𝓇", + "s": "𝓈", + "t": "𝓉", + "u": "𝓊", + "v": "𝓋", + "w": "𝓌", + "x": "𝓍", + "y": "𝓎", + "z": "𝓏", + "A": "𝒜", + "B": "ℬ", + "C": "𝒞", + "D": "𝒟", + "E": "ℰ", + "F": "ℱ", + "G": "𝒢", + "H": "ℋ", + "I": "ℐ", + "J": "𝒥", + "K": "𝒦", + "L": "ℒ", + "M": "ℳ", + "N": "𝒩", + "O": "𝒪", + "P": "𝒫", + "Q": "𝒬", + "R": "ℛ", + "S": "𝒮", + "T": "𝒯", + "U": "𝒰", + "V": "𝒱", + "W": "𝒲", + "X": "𝒳", + "Y": "𝒴", + "Z": "𝒵", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def bold_script(text): + style = { + "a": "𝓪", + "b": "𝓫", + "c": "𝓬", + "d": "𝓭", + "e": "𝓮", + "f": "𝓯", + "g": "𝓰", + "h": "𝓱", + "i": "𝓲", + "j": "𝓳", + "k": "𝓴", + "l": "𝓵", + "m": "𝓶", + "n": "𝓷", + "o": "𝓸", + "p": "𝓹", + "q": "𝓺", + "r": "𝓻", + "s": "𝓼", + "t": "𝓽", + "u": "𝓾", + "v": "𝓿", + "w": "𝔀", + "x": "𝔁", + "y": "𝔂", + "z": "𝔃", + "A": "𝓐", + "B": "𝓑", + "C": "𝓒", + "D": "𝓓", + "E": "𝓔", + "F": "𝓕", + "G": "𝓖", + "H": "𝓗", + "I": "𝓘", + "J": "𝓙", + "K": "𝓚", + "L": "𝓛", + "M": "𝓜", + "N": "𝓝", + "O": "𝓞", + "P": "𝓟", + "Q": "𝓠", + "R": "𝓡", + "S": "𝓢", + "T": "𝓣", + "U": "𝓤", + "V": "𝓥", + "W": "𝓦", + "X": "𝓧", + "Y": "𝓨", + "Z": "𝓩", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def tiny(text): + style = { + "a": "ᵃ", + "b": "ᵇ", + "c": "ᶜ", + "d": "ᵈ", + "e": "ᵉ", + "f": "ᶠ", + "g": "ᵍ", + "h": "ʰ", + "i": "ⁱ", + "j": "ʲ", + "k": "ᵏ", + "l": "ˡ", + "m": "ᵐ", + "n": "ⁿ", + "o": "ᵒ", + "p": "ᵖ", + "q": "ᵠ", + "r": "ʳ", + "s": "ˢ", + "t": "ᵗ", + "u": "ᵘ", + "v": "ᵛ", + "w": "ʷ", + "x": "ˣ", + "y": "ʸ", + "z": "ᶻ", + "A": "ᵃ", + "B": "ᵇ", + "C": "ᶜ", + "D": "ᵈ", + "E": "ᵉ", + "F": "ᶠ", + "G": "ᵍ", + "H": "ʰ", + "I": "ⁱ", + "J": "ʲ", + "K": "ᵏ", + "L": "ˡ", + "M": "ᵐ", + "N": "ⁿ", + "O": "ᵒ", + "P": "ᵖ", + "Q": "ᵠ", + "R": "ʳ", + "S": "ˢ", + "T": "ᵗ", + "U": "ᵘ", + "V": "ᵛ", + "W": "ʷ", + "X": "ˣ", + "Y": "ʸ", + "Z": "ᶻ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def comic(text): + style = { + "a": "ᗩ", + "b": "ᗷ", + "c": "ᑕ", + "d": "ᗪ", + "e": "ᗴ", + "f": "ᖴ", + "g": "ᘜ", + "h": "ᕼ", + "i": "I", + "j": "ᒍ", + "k": "K", + "l": "ᒪ", + "m": "ᗰ", + "n": "ᑎ", + "o": "O", + "p": "ᑭ", + "q": "ᑫ", + "r": "ᖇ", + "s": "Տ", + "t": "T", + "u": "ᑌ", + "v": "ᐯ", + "w": "ᗯ", + "x": "᙭", + "y": "Y", + "z": "ᘔ", + "A": "ᗩ", + "B": "ᗷ", + "C": "ᑕ", + "D": "ᗪ", + "E": "ᗴ", + "F": "ᖴ", + "G": "ᘜ", + "H": "ᕼ", + "I": "I", + "J": "ᒍ", + "K": "K", + "L": "ᒪ", + "M": "ᗰ", + "N": "ᑎ", + "O": "O", + "P": "ᑭ", + "Q": "ᑫ", + "R": "ᖇ", + "S": "Տ", + "T": "T", + "U": "ᑌ", + "V": "ᐯ", + "W": "ᗯ", + "X": "᙭", + "Y": "Y", + "Z": "ᘔ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def san(text): + style = { + "a": "𝗮", + "b": "𝗯", + "c": "𝗰", + "d": "𝗱", + "e": "𝗲", + "f": "𝗳", + "g": "𝗴", + "h": "𝗵", + "i": "𝗶", + "j": "𝗷", + "k": "𝗸", + "l": "𝗹", + "m": "𝗺", + "n": "𝗻", + "o": "𝗼", + "p": "𝗽", + "q": "𝗾", + "r": "𝗿", + "s": "𝘀", + "t": "𝘁", + "u": "𝘂", + "v": "𝘃", + "w": "𝘄", + "x": "𝘅", + "y": "𝘆", + "z": "𝘇", + "A": "𝗔", + "B": "𝗕", + "C": "𝗖", + "D": "𝗗", + "E": "𝗘", + "F": "𝗙", + "G": "𝗚", + "H": "𝗛", + "I": "𝗜", + "J": "𝗝", + "K": "𝗞", + "L": "𝗟", + "M": "𝗠", + "N": "𝗡", + "O": "𝗢", + "P": "𝗣", + "Q": "𝗤", + "R": "𝗥", + "S": "𝗦", + "T": "𝗧", + "U": "𝗨", + "V": "𝗩", + "W": "𝗪", + "X": "𝗫", + "Y": "𝗬", + "Z": "𝗭", + "0": "𝟬", + "1": "𝟭", + "2": "𝟮", + "3": "𝟯", + "4": "𝟰", + "5": "𝟱", + "6": "𝟲", + "7": "𝟳", + "8": "𝟴", + "9": "𝟵", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def slant_san(text): + style = { + "a": "𝙖", + "b": "𝙗", + "c": "𝙘", + "d": "𝙙", + "e": "𝙚", + "f": "𝙛", + "g": "𝙜", + "h": "𝙝", + "i": "𝙞", + "j": "𝙟", + "k": "𝙠", + "l": "𝙡", + "m": "𝙢", + "n": "𝙣", + "o": "𝙤", + "p": "𝙥", + "q": "𝙦", + "r": "𝙧", + "s": "𝙨", + "t": "𝙩", + "u": "𝙪", + "v": "𝙫", + "w": "𝙬", + "x": "𝙭", + "y": "𝙮", + "z": "𝙯", + "A": "𝘼", + "B": "𝘽", + "C": "𝘾", + "D": "𝘿", + "E": "𝙀", + "F": "𝙁", + "G": "𝙂", + "H": "𝙃", + "I": "𝙄", + "J": "𝙅", + "K": "𝙆", + "L": "𝙇", + "M": "𝙈", + "N": "𝙉", + "O": "𝙊", + "P": "𝙋", + "Q": "𝙌", + "R": "𝙍", + "S": "𝙎", + "T": "𝙏", + "U": "𝙐", + "V": "𝙑", + "W": "𝙒", + "X": "𝙓", + "Y": "𝙔", + "Z": "𝙕", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def slant(text): + style = { + "a": "𝘢", + "b": "𝘣", + "c": "𝘤", + "d": "𝘥", + "e": "𝘦", + "f": "𝘧", + "g": "𝘨", + "h": "𝘩", + "i": "𝘪", + "j": "𝘫", + "k": "𝘬", + "l": "𝘭", + "m": "𝘮", + "n": "𝘯", + "o": "𝘰", + "p": "𝘱", + "q": "𝘲", + "r": "𝘳", + "s": "𝘴", + "t": "𝘵", + "u": "𝘶", + "v": "𝘷", + "w": "𝘸", + "x": "𝘹", + "y": "𝘺", + "z": "𝘻", + "A": "𝘈", + "B": "𝘉", + "C": "𝘊", + "D": "𝘋", + "E": "𝘌", + "F": "𝘍", + "G": "𝘎", + "H": "𝘏", + "I": "𝘐", + "J": "𝘑", + "K": "𝘒", + "L": "𝘓", + "M": "𝘔", + "N": "𝘕", + "O": "𝘖", + "P": "𝘗", + "Q": "𝘘", + "R": "𝘙", + "S": "𝘚", + "T": "𝘛", + "U": "𝘜", + "V": "𝘝", + "W": "𝘞", + "X": "𝘟", + "Y": "𝘠", + "Z": "𝘡", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def sim(text): + style = { + "a": "𝖺", + "b": "𝖻", + "c": "𝖼", + "d": "𝖽", + "e": "𝖾", + "f": "𝖿", + "g": "𝗀", + "h": "𝗁", + "i": "𝗂", + "j": "𝗃", + "k": "𝗄", + "l": "𝗅", + "m": "𝗆", + "n": "𝗇", + "o": "𝗈", + "p": "𝗉", + "q": "𝗊", + "r": "𝗋", + "s": "𝗌", + "t": "𝗍", + "u": "𝗎", + "v": "𝗏", + "w": "𝗐", + "x": "𝗑", + "y": "𝗒", + "z": "𝗓", + "A": "𝖠", + "B": "𝖡", + "C": "𝖢", + "D": "𝖣", + "E": "𝖤", + "F": "𝖥", + "G": "𝖦", + "H": "𝖧", + "I": "𝖨", + "J": "𝖩", + "K": "𝖪", + "L": "𝖫", + "M": "𝖬", + "N": "𝖭", + "O": "𝖮", + "P": "𝖯", + "Q": "𝖰", + "R": "𝖱", + "S": "𝖲", + "T": "𝖳", + "U": "𝖴", + "V": "𝖵", + "W": "𝖶", + "X": "𝖷", + "Y": "𝖸", + "Z": "𝖹", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def circles(text): + style = { + "a": "Ⓐ︎", + "b": "Ⓑ︎", + "c": "Ⓒ︎", + "d": "Ⓓ︎", + "e": "Ⓔ︎", + "f": "Ⓕ︎", + "g": "Ⓖ︎", + "h": "Ⓗ︎", + "i": "Ⓘ︎", + "j": "Ⓙ︎", + "k": "Ⓚ︎", + "l": "Ⓛ︎", + "m": "Ⓜ︎", + "n": "Ⓝ︎", + "o": "Ⓞ︎", + "p": "Ⓟ︎", + "q": "Ⓠ︎", + "r": "Ⓡ︎", + "s": "Ⓢ︎", + "t": "Ⓣ︎", + "u": "Ⓤ︎", + "v": "Ⓥ︎", + "w": "Ⓦ︎", + "x": "Ⓧ︎", + "y": "Ⓨ︎", + "z": "Ⓩ︎", + "A": "Ⓐ︎", + "B": "Ⓑ︎", + "C": "Ⓒ︎", + "D": "Ⓓ︎", + "E": "Ⓔ︎", + "F": "Ⓕ︎", + "G": "Ⓖ︎", + "H": "Ⓗ︎", + "I": "Ⓘ︎", + "J": "Ⓙ︎", + "K": "Ⓚ︎", + "L": "Ⓛ︎", + "M": "Ⓜ︎", + "N": "Ⓝ︎", + "O": "Ⓞ︎", + "P": "Ⓟ︎", + "Q": "Ⓠ︎", + "R": "Ⓡ︎", + "S": "Ⓢ︎", + "T": "Ⓣ︎", + "U": "Ⓤ︎", + "V": "Ⓥ︎", + "W": "Ⓦ︎", + "X": "Ⓧ︎", + "Y": "Ⓨ︎", + "Z": "Ⓩ︎", + "0": "⓪", + "1": "①", + "2": "②", + "3": "③", + "4": "④", + "5": "⑤", + "6": "⑥", + "7": "⑦", + "8": "⑧", + "9": "⑨", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def dark_circle(text): + style = { + "a": "🅐︎", + "b": "🅑︎", + "c": "🅒︎", + "d": "🅓︎", + "e": "🅔︎", + "f": "🅕︎", + "g": "🅖︎", + "h": "🅗︎", + "i": "🅘︎", + "j": "🅙︎", + "k": "🅚︎", + "l": "🅛︎", + "m": "🅜︎", + "n": "🅝︎", + "o": "🅞︎", + "p": "🅟︎", + "q": "🅠︎", + "r": "🅡︎", + "s": "🅢︎", + "t": "🅣︎", + "u": "🅤︎", + "v": "🅥︎", + "w": "🅦︎", + "x": "🅧︎", + "y": "🅨︎", + "z": "🅩︎", + "A": "🅐︎", + "B": "🅑︎", + "C": "🅒︎", + "D": "🅓︎", + "E": "🅔︎", + "F": "🅕︎", + "G": "🅖︎", + "H": "🅗︎", + "I": "🅘︎", + "J": "🅙︎", + "K": "🅚︎", + "L": "🅛︎", + "M": "🅜︎", + "N": "🅝︎", + "O": "🅞︎", + "P": "🅟︎", + "Q": "🅠︎", + "R": "🅡︎", + "S": "🅢︎", + "T": "🅣︎", + "U": "🅤︎", + "V": "🅥︎", + "W": "🅦︎", + "X": "🅧︎", + "Y": "🅨︎", + "Z": "🅩", + "0": "⓿", + "1": "➊", + "2": "➋", + "3": "➌", + "4": "➍", + "5": "➎", + "6": "➏", + "7": "➐", + "8": "➑", + "9": "➒", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def gothic(text): + style = { + "a": "𝔞", + "b": "𝔟", + "c": "𝔠", + "d": "𝔡", + "e": "𝔢", + "f": "𝔣", + "g": "𝔤", + "h": "𝔥", + "i": "𝔦", + "j": "𝔧", + "k": "𝔨", + "l": "𝔩", + "m": "𝔪", + "n": "𝔫", + "o": "𝔬", + "p": "𝔭", + "q": "𝔮", + "r": "𝔯", + "s": "𝔰", + "t": "𝔱", + "u": "𝔲", + "v": "𝔳", + "w": "𝔴", + "x": "𝔵", + "y": "𝔶", + "z": "𝔷", + "A": "𝔄", + "B": "𝔅", + "C": "ℭ", + "D": "𝔇", + "E": "𝔈", + "F": "𝔉", + "G": "𝔊", + "H": "ℌ", + "I": "ℑ", + "J": "𝔍", + "K": "𝔎", + "L": "𝔏", + "M": "𝔐", + "N": "𝔑", + "O": "𝔒", + "P": "𝔓", + "Q": "𝔔", + "R": "ℜ", + "S": "𝔖", + "T": "𝔗", + "U": "𝔘", + "V": "𝔙", + "W": "𝔚", + "X": "𝔛", + "Y": "𝔜", + "Z": "ℨ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def bold_gothic(text): + style = { + "a": "𝖆", + "b": "𝖇", + "c": "𝖈", + "d": "𝖉", + "e": "𝖊", + "f": "𝖋", + "g": "𝖌", + "h": "𝖍", + "i": "𝖎", + "j": "𝖏", + "k": "𝖐", + "l": "𝖑", + "m": "𝖒", + "n": "𝖓", + "o": "𝖔", + "p": "𝖕", + "q": "𝖖", + "r": "𝖗", + "s": "𝖘", + "t": "𝖙", + "u": "𝖚", + "v": "𝖛", + "w": "𝖜", + "x": "𝖝", + "y": "𝖞", + "z": "𝖟", + "A": "𝕬", + "B": "𝕭", + "C": "𝕮", + "D": "𝕺", + "E": "𝕰", + "F": "𝕱", + "G": "𝕲", + "H": "𝕳", + "I": "𝕴", + "J": "𝕵", + "K": "𝕶", + "L": "𝕷", + "M": "𝕸", + "N": "𝕹", + "O": "𝕺", + "P": "𝕻", + "Q": "𝕼", + "R": "𝕽", + "S": "𝕾", + "T": "𝕿", + "U": "𝖀", + "V": "𝖁", + "W": "𝖂", + "X": "𝖃", + "Y": "𝖄", + "Z": "𝖅", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def cloud(text): + style = { + "a": "a͜͡", + "b": "b͜͡", + "c": "c͜͡", + "d": "d͜͡", + "e": "e͜͡", + "f": "f͜͡", + "g": "g͜͡", + "h": "h͜͡", + "i": "i͜͡", + "j": "j͜͡", + "k": "k͜͡", + "l": "l͜͡", + "m": "m͜͡", + "n": "n͜͡", + "o": "o͜͡", + "p": "p͜͡", + "q": "q͜͡", + "r": "r͜͡", + "s": "s͜͡", + "t": "t͜͡", + "u": "u͜͡", + "v": "v͜͡", + "w": "w͜͡", + "x": "x͜͡", + "y": "y͜͡", + "z": "z͜͡", + "A": "A͜͡", + "B": "B͜͡", + "C": "C͜͡", + "D": "D͜͡", + "E": "E͜͡", + "F": "F͜͡", + "G": "G͜͡", + "H": "H͜͡", + "I": "I͜͡", + "J": "J͜͡", + "K": "K͜͡", + "L": "L͜͡", + "M": "M͜͡", + "N": "N͜͡", + "O": "O͜͡", + "P": "P͜͡", + "Q": "Q͜͡", + "R": "R͜͡", + "S": "S͜͡", + "T": "T͜͡", + "U": "U͜͡", + "V": "V͜͡", + "W": "W͜͡", + "X": "X͜͡", + "Y": "Y͜͡", + "Z": "Z͜͡", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def happy(text): + style = { + "a": "ă̈", + "b": "b̆̈", + "c": "c̆̈", + "d": "d̆̈", + "e": "ĕ̈", + "f": "f̆̈", + "g": "ğ̈", + "h": "h̆̈", + "i": "ĭ̈", + "j": "j̆̈", + "k": "k̆̈", + "l": "l̆̈", + "m": "m̆̈", + "n": "n̆̈", + "o": "ŏ̈", + "p": "p̆̈", + "q": "q̆̈", + "r": "r̆̈", + "s": "s̆̈", + "t": "t̆̈", + "u": "ŭ̈", + "v": "v̆̈", + "w": "w̆̈", + "x": "x̆̈", + "y": "y̆̈", + "z": "z̆̈", + "A": "Ă̈", + "B": "B̆̈", + "C": "C̆̈", + "D": "D̆̈", + "E": "Ĕ̈", + "F": "F̆̈", + "G": "Ğ̈", + "H": "H̆̈", + "I": "Ĭ̈", + "J": "J̆̈", + "K": "K̆̈", + "L": "L̆̈", + "M": "M̆̈", + "N": "N̆̈", + "O": "Ŏ̈", + "P": "P̆̈", + "Q": "Q̆̈", + "R": "R̆̈", + "S": "S̆̈", + "T": "T̆̈", + "U": "Ŭ̈", + "V": "V̆̈", + "W": "W̆̈", + "X": "X̆̈", + "Y": "Y̆̈", + "Z": "Z̆̈", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def sad(text): + style = { + "a": "ȃ̈", + "b": "b̑̈", + "c": "c̑̈", + "d": "d̑̈", + "e": "ȇ̈", + "f": "f̑̈", + "g": "g̑̈", + "h": "h̑̈", + "i": "ȋ̈", + "j": "j̑̈", + "k": "k̑̈", + "l": "l̑̈", + "m": "m̑̈", + "n": "n̑̈", + "o": "ȏ̈", + "p": "p̑̈", + "q": "q̑̈", + "r": "ȓ̈", + "s": "s̑̈", + "t": "t̑̈", + "u": "ȗ̈", + "v": "v̑̈", + "w": "w̑̈", + "x": "x̑̈", + "y": "y̑̈", + "z": "z̑̈", + "A": "Ȃ̈", + "B": "B̑̈", + "C": "C̑̈", + "D": "D̑̈", + "E": "Ȇ̈", + "F": "F̑̈", + "G": "G̑̈", + "H": "H̑̈", + "I": "Ȋ̈", + "J": "J̑̈", + "K": "K̑̈", + "L": "L̑̈", + "M": "M̑̈", + "N": "N̑̈", + "O": "Ȏ̈", + "P": "P̑̈", + "Q": "Q̑̈", + "R": "Ȓ̈", + "S": "S̑̈", + "T": "T̑̈", + "U": "Ȗ̈", + "V": "V̑̈", + "W": "W̑̈", + "X": "X̑̈", + "Y": "Y̑̈", + "Z": "Z̑̈", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def special(text): + style = { + "a": "🇦 ", + "b": "🇧 ", + "c": "🇨 ", + "d": "🇩 ", + "e": "🇪 ", + "f": "🇫 ", + "g": "🇬 ", + "h": "🇭 ", + "i": "🇮 ", + "j": "🇯 ", + "k": "🇰 ", + "l": "🇱 ", + "m": "🇲 ", + "n": "🇳 ", + "o": "🇴 ", + "p": "🇵 ", + "q": "🇶 ", + "r": "🇷 ", + "s": "🇸 ", + "t": "🇹 ", + "u": "🇺 ", + "v": "🇻 ", + "w": "🇼 ", + "x": "🇽 ", + "y": "🇾 ", + "z": "🇿 ", + "A": "🇦 ", + "B": "🇧 ", + "C": "🇨 ", + "D": "🇩 ", + "E": "🇪 ", + "F": "🇫 ", + "G": "🇬 ", + "H": "🇭 ", + "I": "🇮 ", + "J": "🇯 ", + "K": "🇰 ", + "L": "🇱 ", + "M": "🇲 ", + "N": "🇳 ", + "O": "🇴 ", + "P": "🇵 ", + "Q": "🇶 ", + "R": "🇷 ", + "S": "🇸 ", + "T": "🇹 ", + "U": "🇺 ", + "V": "🇻 ", + "W": "🇼 ", + "X": "🇽 ", + "Y": "🇾 ", + "Z": "🇿 ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def square(text): + style = { + "a": "🄰", + "b": "🄱", + "c": "🄲", + "d": "🄳", + "e": "🄴", + "f": "🄵", + "g": "🄶", + "h": "🄷", + "i": "🄸", + "j": "🄹", + "k": "🄺", + "l": "🄻", + "m": "🄼", + "n": "🄽", + "o": "🄾", + "p": "🄿", + "q": "🅀", + "r": "🅁", + "s": "🅂", + "t": "🅃", + "u": "🅄", + "v": "🅅", + "w": "🅆", + "x": "🅇", + "y": "🅈", + "z": "🅉", + "A": "🄰", + "B": "🄱", + "C": "🄲", + "D": "🄳", + "E": "🄴", + "F": "🄵", + "G": "🄶", + "H": "🄷", + "I": "🄸", + "J": "🄹", + "K": "🄺", + "L": "🄻", + "M": "🄼", + "N": "🄽", + "O": "🄾", + "P": "🄿", + "Q": "🅀", + "R": "🅁", + "S": "🅂", + "T": "🅃", + "U": "🅄", + "V": "🅅", + "W": "🅆", + "X": "🅇", + "Y": "🅈", + "Z": "🅉", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def dark_square(text): + style = { + "a": "🅰︎", + "b": "🅱︎", + "c": "🅲︎", + "d": "🅳︎", + "e": "🅴︎", + "f": "🅵︎", + "g": "🅶︎", + "h": "🅷︎", + "i": "🅸︎", + "j": "🅹︎", + "k": "🅺︎", + "l": "🅻︎", + "m": "🅼︎", + "n": "🅽︎", + "o": "🅾︎", + "p": "🅿︎", + "q": "🆀︎", + "r": "🆁︎", + "s": "🆂︎", + "t": "🆃︎", + "u": "🆄︎", + "v": "🆅︎", + "w": "🆆︎", + "x": "🆇︎", + "y": "🆈︎", + "z": "🆉︎", + "A": "🅰︎", + "B": "🅱︎", + "C": "🅲︎", + "D": "🅳︎", + "E": "🅴︎", + "F": "🅵︎", + "G": "🅶︎", + "H": "🅷︎", + "I": "🅸︎", + "J": "🅹︎", + "K": "🅺︎", + "L": "🅻︎", + "M": "🅼︎", + "N": "🅽︎", + "O": "🅾︎", + "P": "🅿︎", + "Q": "🆀︎", + "R": "🆁︎", + "S": "🆂︎", + "T": "🆃︎", + "U": "🆄︎", + "V": "🆅︎", + "W": "🆆︎", + "X": "🆇︎", + "Y": "🆈︎", + "Z": "🆉︎", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def andalucia(text): + style = { + "a": "ꪖ", + "b": "᥇", + "c": "ᥴ", + "d": "ᦔ", + "e": "ꫀ", + "f": "ᠻ", + "g": "ᧁ", + "h": "ꫝ", + "i": "𝓲", + "j": "𝓳", + "k": "𝘬", + "l": "ꪶ", + "m": "ꪑ", + "n": "ꪀ", + "o": "ꪮ", + "p": "ρ", + "q": "𝘲", + "r": "𝘳", + "s": "𝘴", + "t": "𝓽", + "u": "ꪊ", + "v": "ꪜ", + "w": "᭙", + "x": "᥊", + "y": "ꪗ", + "z": "ɀ", + "A": "ꪖ", + "B": "᥇", + "C": "ᥴ", + "D": "ᦔ", + "E": "ꫀ", + "F": "ᠻ", + "G": "ᧁ", + "H": "ꫝ", + "I": "𝓲", + "J": "𝓳", + "K": "𝘬", + "L": "ꪶ", + "M": "ꪑ", + "N": "ꪀ", + "O": "ꪮ", + "P": "ρ", + "Q": "𝘲", + "R": "𝘳", + "S": "𝘴", + "T": "𝓽", + "U": "ꪊ", + "V": "ꪜ", + "W": "᭙", + "X": "᥊", + "Y": "ꪗ", + "Z": "ɀ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def manga(text): + style = { + "a": "卂", + "b": "乃", + "c": "匚", + "d": "ᗪ", + "e": "乇", + "f": "千", + "g": "ᘜ", + "h": "卄", + "i": "|", + "j": "フ", + "k": "Ҝ", + "l": "ㄥ", + "m": "爪", + "n": "几", + "o": "ㄖ", + "p": "卩", + "q": "Ҩ", + "r": "尺", + "s": "丂", + "t": "ㄒ", + "u": "ㄩ", + "v": "ᐯ", + "w": "山", + "x": "乂", + "y": "ㄚ", + "z": "乙", + "A": "卂", + "B": "乃", + "C": "匚", + "D": "ᗪ", + "E": "乇", + "F": "千", + "G": "ᘜ", + "H": "卄", + "I": "|", + "J": "フ", + "K": "Ҝ", + "L": "ㄥ", + "M": "爪", + "N": "几", + "O": "ㄖ", + "P": "卩", + "Q": "Ҩ", + "R": "尺", + "S": "丂", + "T": "ㄒ", + "U": "ㄩ", + "V": "ᐯ", + "W": "山", + "X": "乂", + "Y": "ㄚ", + "Z": "乙", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def stinky(text): + style = { + "a": "a̾", + "b": "b̾", + "c": "c̾", + "d": "d̾", + "e": "e̾", + "f": "f̾", + "g": "g̾", + "h": "h̾", + "i": "i̾", + "j": "j̾", + "k": "k̾", + "l": "l̾", + "m": "m̾", + "n": "n̾", + "o": "o̾", + "p": "p̾", + "q": "q̾", + "r": "r̾", + "s": "s̾", + "t": "t̾", + "u": "u̾", + "v": "v̾", + "w": "w̾", + "x": "x̾", + "y": "y̾", + "z": "z̾", + "A": "A̾", + "B": "B̾", + "C": "C̾", + "D": "D̾", + "E": "E̾", + "F": "F̾", + "G": "G̾", + "H": "H̾", + "I": "I̾", + "J": "J̾", + "K": "K̾", + "L": "L̾", + "M": "M̾", + "N": "N̾", + "O": "O̾", + "P": "P̾", + "Q": "Q̾", + "R": "R̾", + "S": "S̾", + "T": "T̾", + "U": "U̾", + "V": "V̾", + "W": "W̾", + "X": "X̾", + "Y": "Y̾", + "Z": "Z̾", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def bubbles(text): + style = { + "a": "ḁͦ", + "b": "b̥ͦ", + "c": "c̥ͦ", + "d": "d̥ͦ", + "e": "e̥ͦ", + "f": "f̥ͦ", + "g": "g̥ͦ", + "h": "h̥ͦ", + "i": "i̥ͦ", + "j": "j̥ͦ", + "k": "k̥ͦ", + "l": "l̥ͦ", + "m": "m̥ͦ", + "n": "n̥ͦ", + "o": "o̥ͦ", + "p": "p̥ͦ", + "q": "q̥ͦ", + "r": "r̥ͦ", + "s": "s̥ͦ", + "t": "t̥ͦ", + "u": "u̥ͦ", + "v": "v̥ͦ", + "w": "w̥ͦ", + "x": "x̥ͦ", + "y": "y̥ͦ", + "z": "z̥ͦ", + "A": "Ḁͦ", + "B": "B̥ͦ", + "C": "C̥ͦ", + "D": "D̥ͦ", + "E": "E̥ͦ", + "F": "F̥ͦ", + "G": "G̥ͦ", + "H": "H̥ͦ", + "I": "I̥ͦ", + "J": "J̥ͦ", + "K": "K̥ͦ", + "L": "L̥ͦ", + "M": "M̥ͦ", + "N": "N̥ͦ", + "O": "O̥ͦ", + "P": "P̥ͦ", + "Q": "Q̥ͦ", + "R": "R̥ͦ", + "S": "S̥ͦ", + "T": "T̥ͦ", + "U": "U̥ͦ", + "V": "V̥ͦ", + "W": "W̥ͦ", + "X": "X̥ͦ", + "Y": "Y̥ͦ", + "Z": "Z̥ͦ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def underline(text): + style = { + "a": "a͟", + "b": "b͟", + "c": "c͟", + "d": "d͟", + "e": "e͟", + "f": "f͟", + "g": "g͟", + "h": "h͟", + "i": "i͟", + "j": "j͟", + "k": "k͟", + "l": "l͟", + "m": "m͟", + "n": "n͟", + "o": "o͟", + "p": "p͟", + "q": "q͟", + "r": "r͟", + "s": "s͟", + "t": "t͟", + "u": "u͟", + "v": "v͟", + "w": "w͟", + "x": "x͟", + "y": "y͟", + "z": "z͟", + "A": "A͟", + "B": "B͟", + "C": "C͟", + "D": "D͟", + "E": "E͟", + "F": "F͟", + "G": "G͟", + "H": "H͟", + "I": "I͟", + "J": "J͟", + "K": "K͟", + "L": "L͟", + "M": "M͟", + "N": "N͟", + "O": "O͟", + "P": "P͟", + "Q": "Q͟", + "R": "R͟", + "S": "S͟", + "T": "T͟", + "U": "U͟", + "V": "V͟", + "W": "W͟", + "X": "X͟", + "Y": "Y͟", + "Z": "Z͟", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def ladybug(text): + style = { + "a": "ꍏ", + "b": "ꌃ", + "c": "ꏳ", + "d": "ꀷ", + "e": "ꏂ", + "f": "ꎇ", + "g": "ꁅ", + "h": "ꀍ", + "i": "ꀤ", + "j": "꒻", + "k": "ꀘ", + "l": "꒒", + "m": "ꎭ", + "n": "ꈤ", + "o": "ꂦ", + "p": "ᖘ", + "q": "ꆰ", + "r": "ꋪ", + "s": "ꌚ", + "t": "꓄", + "u": "ꀎ", + "v": "꒦", + "w": "ꅐ", + "x": "ꉧ", + "y": "ꌩ", + "z": "ꁴ", + "A": "ꍏ", + "B": "ꌃ", + "C": "ꏳ", + "D": "ꀷ", + "E": "ꏂ", + "F": "ꎇ", + "G": "ꁅ", + "H": "ꀍ", + "I": "ꀤ", + "J": "꒻", + "K": "ꀘ", + "L": "꒒", + "M": "ꎭ", + "N": "ꈤ", + "O": "ꂦ", + "P": "ᖘ", + "Q": "ꆰ", + "R": "ꋪ", + "S": "ꌚ", + "T": "꓄", + "U": "ꀎ", + "V": "꒦", + "W": "ꅐ", + "X": "ꉧ", + "Y": "ꌩ", + "Z": "ꁴ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def rays(text): + style = { + "a": "a҉", + "b": "b҉", + "c": "c҉", + "d": "d҉", + "e": "e҉", + "f": "f҉", + "g": "g҉", + "h": "h҉", + "i": "i҉", + "j": "j҉", + "k": "k҉", + "l": "l҉", + "m": "m҉", + "n": "n҉", + "o": "o҉", + "p": "p҉", + "q": "q҉", + "r": "r҉", + "s": "s҉", + "t": "t҉", + "u": "u҉", + "v": "v҉", + "w": "w҉", + "x": "x҉", + "y": "y҉", + "z": "z҉", + "A": "A҉", + "B": "B҉", + "C": "C҉", + "D": "D҉", + "E": "E҉", + "F": "F҉", + "G": "G҉", + "H": "H҉", + "I": "I҉", + "J": "J҉", + "K": "K҉", + "L": "L҉", + "M": "M҉", + "N": "N҉", + "O": "O҉", + "P": "P҉", + "Q": "Q҉", + "R": "R҉", + "S": "S҉", + "T": "T҉", + "U": "U҉", + "V": "V҉", + "W": "W҉", + "X": "X҉", + "Y": "Y҉", + "Z": "Z҉", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def birds(text): + style = { + "a": "a҈", + "b": "b҈", + "c": "c҈", + "d": "d҈", + "e": "e҈", + "f": "f҈", + "g": "g҈", + "h": "h҈", + "i": "i҈", + "j": "j҈", + "k": "k҈", + "l": "l҈", + "m": "m҈", + "n": "n҈", + "o": "o҈", + "p": "p҈", + "q": "q҈", + "r": "r҈", + "s": "s҈", + "t": "t҈", + "u": "u҈", + "v": "v҈", + "w": "w҈", + "x": "x҈", + "y": "y҈", + "z": "z҈", + "A": "A҈", + "B": "B҈", + "C": "C҈", + "D": "D҈", + "E": "E҈", + "F": "F҈", + "G": "G҈", + "H": "H҈", + "I": "I҈", + "J": "J҈", + "K": "K҈", + "L": "L҈", + "M": "M҈", + "N": "N҈", + "O": "O҈", + "P": "P҈", + "Q": "Q҈", + "R": "R҈", + "S": "S҈", + "T": "T҈", + "U": "U҈", + "V": "V҈", + "W": "W҈", + "X": "X҈", + "Y": "Y҈", + "Z": "Z҈", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def slash(text): + style = { + "a": "a̸", + "b": "b̸", + "c": "c̸", + "d": "d̸", + "e": "e̸", + "f": "f̸", + "g": "g̸", + "h": "h̸", + "i": "i̸", + "j": "j̸", + "k": "k̸", + "l": "l̸", + "m": "m̸", + "n": "n̸", + "o": "o̸", + "p": "p̸", + "q": "q̸", + "r": "r̸", + "s": "s̸", + "t": "t̸", + "u": "u̸", + "v": "v̸", + "w": "w̸", + "x": "x̸", + "y": "y̸", + "z": "z̸", + "A": "A̸", + "B": "B̸", + "C": "C̸", + "D": "D̸", + "E": "E̸", + "F": "F̸", + "G": "G̸", + "H": "H̸", + "I": "I̸", + "J": "J̸", + "K": "K̸", + "L": "L̸", + "M": "M̸", + "N": "N̸", + "O": "O̸", + "P": "P̸", + "Q": "Q̸", + "R": "R̸", + "S": "S̸", + "T": "T̸", + "U": "U̸", + "V": "V̸", + "W": "W̸", + "X": "X̸", + "Y": "Y̸", + "Z": "Z̸", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def stop(text): + style = { + "a": "a⃠", + "b": "b⃠", + "c": "c⃠", + "d": "d⃠", + "e": "e⃠", + "f": "f⃠", + "g": "g⃠", + "h": "h⃠", + "i": "i⃠", + "j": "j⃠", + "k": "k⃠", + "l": "l⃠", + "m": "m⃠", + "n": "n⃠", + "o": "o⃠", + "p": "p⃠", + "q": "q⃠", + "r": "r⃠", + "s": "s⃠", + "t": "t⃠", + "u": "u⃠", + "v": "v⃠", + "w": "w⃠", + "x": "x⃠", + "y": "y⃠", + "z": "z⃠", + "A": "A⃠", + "B": "B⃠", + "C": "C⃠", + "D": "D⃠", + "E": "E⃠", + "F": "F⃠", + "G": "G⃠", + "H": "H⃠", + "I": "I⃠", + "J": "J⃠", + "K": "K⃠", + "L": "L⃠", + "M": "M⃠", + "N": "N⃠", + "O": "O⃠", + "P": "P⃠", + "Q": "Q⃠", + "R": "R⃠", + "S": "S⃠", + "T": "T⃠", + "U": "U⃠", + "V": "V⃠", + "W": "W⃠", + "X": "X⃠", + "Y": "Y⃠", + "Z": "Z⃠", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def skyline(text): + style = { + "a": "a̺͆", + "b": "b̺͆", + "c": "c̺͆", + "d": "d̺͆", + "e": "e̺͆", + "f": "f̺͆", + "g": "g̺͆", + "h": "h̺͆", + "i": "i̺͆", + "j": "j̺͆", + "k": "k̺͆", + "l": "l̺͆", + "m": "m̺͆", + "n": "n̺͆", + "o": "o̺͆", + "p": "p̺͆", + "q": "q̺͆", + "r": "r̺͆", + "s": "s̺͆", + "t": "t̺͆", + "u": "u̺͆", + "v": "v̺͆", + "w": "w̺͆", + "x": "x̺͆", + "y": "y̺͆", + "z": "z̺͆", + "A": "A̺͆", + "B": "B̺͆", + "C": "C̺͆", + "D": "D̺͆", + "E": "E̺͆", + "F": "F̺͆", + "G": "G̺͆", + "H": "H̺͆", + "I": "I̺͆", + "J": "J̺͆", + "K": "K̺͆", + "L": "L̺͆", + "M": "M̺͆", + "N": "N̺͆", + "O": "O̺͆", + "P": "P̺͆", + "Q": "Q̺͆", + "R": "R̺͆", + "S": "S̺͆", + "T": "T̺͆", + "U": "U̺͆", + "V": "V̺͆", + "W": "W̺͆", + "X": "X̺͆", + "Y": "Y̺͆", + "Z": "Z̺͆", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def arrows(text): + style = { + "a": "a͎", + "b": "b͎", + "c": "c͎", + "d": "d͎", + "e": "e͎", + "f": "f͎", + "g": "g͎", + "h": "h͎", + "i": "i͎", + "j": "j͎", + "k": "k͎", + "l": "l͎", + "m": "m͎", + "n": "n͎", + "o": "o͎", + "p": "p͎", + "q": "q͎", + "r": "r͎", + "s": "s͎", + "t": "t͎", + "u": "u͎", + "v": "v͎", + "w": "w͎", + "x": "x͎", + "y": "y͎", + "z": "z͎", + "A": "A͎", + "B": "B͎", + "C": "C͎", + "D": "D͎", + "E": "E͎", + "F": "F͎", + "G": "G͎", + "H": "H͎", + "I": "I͎", + "J": "J͎", + "K": "K͎", + "L": "L͎", + "M": "M͎", + "N": "N͎", + "O": "O͎", + "P": "P͎", + "Q": "Q͎", + "R": "R͎", + "S": "S͎", + "T": "T͎", + "U": "U͎", + "V": "V͎", + "W": "W͎", + "X": "X͎", + "Y": "Y͎", + "Z": "Z͎", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def rvnes(text): + style = { + "a": "ል", + "b": "ጌ", + "c": "ር", + "d": "ዕ", + "e": "ቿ", + "f": "ቻ", + "g": "ኗ", + "h": "ዘ", + "i": "ጎ", + "j": "ጋ", + "k": "ጕ", + "l": "ረ", + "m": "ጠ", + "n": "ክ", + "o": "ዐ", + "p": "የ", + "q": "ዒ", + "r": "ዪ", + "s": "ነ", + "t": "ፕ", + "u": "ሁ", + "v": "ሀ", + "w": "ሠ", + "x": "ሸ", + "y": "ሃ", + "z": "ጊ", + "A": "ል", + "B": "ጌ", + "C": "ር", + "D": "ዕ", + "E": "ቿ", + "F": "ቻ", + "G": "ኗ", + "H": "ዘ", + "I": "ጎ", + "J": "ጋ", + "K": "ጕ", + "L": "ረ", + "M": "ጠ", + "N": "ክ", + "O": "ዐ", + "P": "የ", + "Q": "ዒ", + "R": "ዪ", + "S": "ነ", + "T": "ፕ", + "U": "ሁ", + "V": "ሀ", + "W": "ሠ", + "X": "ሸ", + "Y": "ሃ", + "Z": "ጊ", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def strike(text): + style = { + "a": "a̶", + "b": "b̶", + "c": "c̶", + "d": "d̶", + "e": "e̶", + "f": "f̶", + "g": "g̶", + "h": "h̶", + "i": "i̶", + "j": "j̶", + "k": "k̶", + "l": "l̶", + "m": "m̶", + "n": "n̶", + "o": "o̶", + "p": "p̶", + "q": "q̶", + "r": "r̶", + "s": "s̶", + "t": "t̶", + "u": "u̶", + "v": "v̶", + "w": "w̶", + "x": "x̶", + "y": "y̶", + "z": "z̶", + "A": "A̶", + "B": "B̶", + "C": "C̶", + "D": "D̶", + "E": "E̶", + "F": "F̶", + "G": "G̶", + "H": "H̶", + "I": "I̶", + "J": "J̶", + "K": "K̶", + "L": "L̶", + "M": "M̶", + "N": "N̶", + "O": "O̶", + "P": "P̶", + "Q": "Q̶", + "R": "R̶", + "S": "S̶", + "T": "T̶", + "U": "U̶", + "V": "V̶", + "W": "W̶", + "X": "X̶", + "Y": "Y̶", + "Z": "Z̶", + } + for i, j in style.items(): + text = text.replace(i, j) + return text + + def frozen(text): + style = { + "a": "a༙", + "b": "b༙", + "c": "c༙", + "d": "d༙", + "e": "e༙", + "f": "f༙", + "g": "g༙", + "h": "h༙", + "i": "i༙", + "j": "j༙", + "k": "k༙", + "l": "l༙", + "m": "m༙", + "n": "n༙", + "o": "o༙", + "p": "p༙", + "q": "q༙", + "r": "r༙", + "s": "s༙", + "t": "t༙", + "u": "u༙", + "v": "v༙", + "w": "w༙", + "x": "x༙", + "y": "y༙", + "z": "z༙", + "A": "A༙", + "B": "B༙", + "C": "C༙", + "D": "D༙", + "E": "E༙", + "F": "F༙", + "G": "G༙", + "H": "H༙", + "I": "I༙", + "J": "J༙", + "K": "K༙", + "L": "L༙", + "M": "M༙", + "N": "N༙", + "O": "O༙", + "P": "P༙", + "Q": "Q༙", + "R": "R༙", + "S": "S༙", + "T": "T༙", + "U": "U༙", + "V": "V༙", + "W": "W༙", + "X": "X༙", + "Y": "Y༙", + "Z": "Z༙", + } + for i, j in style.items(): + text = text.replace(i, j) + return text diff --git a/Database/sql/forceSubscribe_sql.py b/Database/sql/forceSubscribe_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..9c36b93e3608afc721d627583e7412225d099106 --- /dev/null +++ b/Database/sql/forceSubscribe_sql.py @@ -0,0 +1,46 @@ +from sqlalchemy import Column, Numeric, String + +from Database.sql import BASE, SESSION + + +class forceSubscribe(BASE): + __tablename__ = "forceSubscribe" + chat_id = Column(Numeric, primary_key=True) + channel = Column(String) + + def __init__(self, chat_id, channel): + self.chat_id = chat_id + self.channel = channel + + +forceSubscribe.__table__.create(checkfirst=True) + + +def fs_settings(chat_id): + try: + return ( + SESSION.query(forceSubscribe) + .filter(forceSubscribe.chat_id == chat_id) + .one() + ) + except: + return None + finally: + SESSION.close() + + +def add_channel(chat_id, channel): + adder = SESSION.query(forceSubscribe).get(chat_id) + if adder: + adder.channel = channel + else: + adder = forceSubscribe(chat_id, channel) + SESSION.add(adder) + SESSION.commit() + + +def disapprove(chat_id): + rem = SESSION.query(forceSubscribe).get(chat_id) + if rem: + SESSION.delete(rem) + SESSION.commit() diff --git a/Database/sql/global_bans_sql.py b/Database/sql/global_bans_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..436320c731b1f2c322fb758950a819ed2ebe8e7d --- /dev/null +++ b/Database/sql/global_bans_sql.py @@ -0,0 +1,167 @@ +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText + +from Database.sql import BASE, SESSION + + +class GloballyBannedUsers(BASE): + __tablename__ = "gbans" + user_id = Column(BigInteger, primary_key=True) + name = Column(UnicodeText, nullable=False) + reason = Column(UnicodeText) + + def __init__(self, user_id, name, reason=None): + self.user_id = user_id + self.name = name + self.reason = reason + + def __repr__(self): + return "".format(self.name, self.user_id) + + def to_dict(self): + return {"user_id": self.user_id, "name": self.name, "reason": self.reason} + + +class GbanSettings(BASE): + __tablename__ = "gban_settings" + chat_id = Column(String(14), primary_key=True) + setting = Column(Boolean, default=True, nullable=False) + + def __init__(self, chat_id, enabled): + self.chat_id = str(chat_id) + self.setting = enabled + + def __repr__(self): + return "".format(self.chat_id, self.setting) + + +GloballyBannedUsers.__table__.create(checkfirst=True) +GbanSettings.__table__.create(checkfirst=True) + +GBANNED_USERS_LOCK = threading.RLock() +GBAN_SETTING_LOCK = threading.RLock() +GBANNED_LIST = set() +GBANSTAT_LIST = set() + + +def gban_user(user_id, name, reason=None): + with GBANNED_USERS_LOCK: + user = SESSION.query(GloballyBannedUsers).get(user_id) + if not user: + user = GloballyBannedUsers(user_id, name, reason) + else: + user.name = name + user.reason = reason + + SESSION.merge(user) + SESSION.commit() + __load_gbanned_userid_list() + + +def update_gban_reason(user_id, name, reason=None): + with GBANNED_USERS_LOCK: + user = SESSION.query(GloballyBannedUsers).get(user_id) + if not user: + return None + old_reason = user.reason + user.name = name + user.reason = reason + + SESSION.merge(user) + SESSION.commit() + return old_reason + + +def ungban_user(user_id): + with GBANNED_USERS_LOCK: + user = SESSION.query(GloballyBannedUsers).get(user_id) + if user: + SESSION.delete(user) + + SESSION.commit() + __load_gbanned_userid_list() + + +def is_user_gbanned(user_id): + return user_id in GBANNED_LIST + + +def get_gbanned_user(user_id): + try: + return SESSION.query(GloballyBannedUsers).get(user_id) + finally: + SESSION.close() + + +def get_gban_list(): + try: + return [x.to_dict() for x in SESSION.query(GloballyBannedUsers).all()] + finally: + SESSION.close() + + +def enable_gbans(chat_id): + with GBAN_SETTING_LOCK: + chat = SESSION.query(GbanSettings).get(str(chat_id)) + if not chat: + chat = GbanSettings(chat_id, True) + + chat.setting = True + SESSION.add(chat) + SESSION.commit() + if str(chat_id) in GBANSTAT_LIST: + GBANSTAT_LIST.remove(str(chat_id)) + + +def disable_gbans(chat_id): + with GBAN_SETTING_LOCK: + chat = SESSION.query(GbanSettings).get(str(chat_id)) + if not chat: + chat = GbanSettings(chat_id, False) + + chat.setting = False + SESSION.add(chat) + SESSION.commit() + GBANSTAT_LIST.add(str(chat_id)) + + +def does_chat_gban(chat_id): + return str(chat_id) not in GBANSTAT_LIST + + +def num_gbanned_users(): + return len(GBANNED_LIST) + + +def __load_gbanned_userid_list(): + global GBANNED_LIST + try: + GBANNED_LIST = {x.user_id for x in SESSION.query(GloballyBannedUsers).all()} + finally: + SESSION.close() + + +def __load_gban_stat_list(): + global GBANSTAT_LIST + try: + GBANSTAT_LIST = { + x.chat_id for x in SESSION.query(GbanSettings).all() if not x.setting + } + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with GBAN_SETTING_LOCK: + chat = SESSION.query(GbanSettings).get(str(old_chat_id)) + if chat: + chat.chat_id = new_chat_id + SESSION.add(chat) + + SESSION.commit() + + +# Create in memory userid to avoid disk access +__load_gbanned_userid_list() +__load_gban_stat_list() diff --git a/Database/sql/kuki_sql.py b/Database/sql/kuki_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..3b7bfff3d6aa2db2fe27fca086dce23aca23b672 --- /dev/null +++ b/Database/sql/kuki_sql.py @@ -0,0 +1,42 @@ +import threading + +from sqlalchemy import Column, String + +from Database.sql import BASE, SESSION + + +class KukiChats(BASE): + __tablename__ = "kuki_chats" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +KukiChats.__table__.create(checkfirst=True) +INSERTION_LOCK = threading.RLock() + + +def is_kuki(chat_id): + try: + chat = SESSION.query(KukiChats).get(str(chat_id)) + return bool(chat) + finally: + SESSION.close() + + +def set_kuki(chat_id): + with INSERTION_LOCK: + kukichat = SESSION.query(KukiChats).get(str(chat_id)) + if not kukichat: + kukichat = KukiChats(str(chat_id)) + SESSION.add(kukichat) + SESSION.commit() + + +def rem_kuki(chat_id): + with INSERTION_LOCK: + kukichat = SESSION.query(KukiChats).get(str(chat_id)) + if kukichat: + SESSION.delete(kukichat) + SESSION.commit() diff --git a/Database/sql/locks_sql.py b/Database/sql/locks_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..3fd71b09d0befeca0e05259e6259ecd22a014a9c --- /dev/null +++ b/Database/sql/locks_sql.py @@ -0,0 +1,292 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# New chat added -> setup permissions +import threading + +from sqlalchemy import Boolean, Column, String + +from Database.sql import BASE, SESSION + + +class Permissions(BASE): + __tablename__ = "permissions" + chat_id = Column(String(14), primary_key=True) + # Booleans are for "is this locked", _NOT_ "is this allowed" + audio = Column(Boolean, default=False) + voice = Column(Boolean, default=False) + contact = Column(Boolean, default=False) + video = Column(Boolean, default=False) + document = Column(Boolean, default=False) + photo = Column(Boolean, default=False) + sticker = Column(Boolean, default=False) + gif = Column(Boolean, default=False) + url = Column(Boolean, default=False) + bots = Column(Boolean, default=False) + forward = Column(Boolean, default=False) + game = Column(Boolean, default=False) + location = Column(Boolean, default=False) + rtl = Column(Boolean, default=False) + button = Column(Boolean, default=False) + egame = Column(Boolean, default=False) + inline = Column(Boolean, default=False) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) # ensure string + self.audio = False + self.voice = False + self.contact = False + self.video = False + self.document = False + self.photo = False + self.sticker = False + self.gif = False + self.url = False + self.bots = False + self.forward = False + self.game = False + self.location = False + self.rtl = False + self.button = False + self.egame = False + self.inline = False + + def __repr__(self): + return "<ᴘᴇʀᴍɪssɪᴏɴs ғᴏʀ %s>" % self.chat_id + + +class Restrictions(BASE): + __tablename__ = "restrictions" + chat_id = Column(String(14), primary_key=True) + # Booleans are for "is this restricted", _NOT_ "is this allowed" + messages = Column(Boolean, default=False) + media = Column(Boolean, default=False) + other = Column(Boolean, default=False) + preview = Column(Boolean, default=False) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) # ensure string + self.messages = False + self.media = False + self.other = False + self.preview = False + + def __repr__(self): + return "<ʀᴇsᴛʀɪᴄᴛɪᴏɴs ғᴏʀ %s>" % self.chat_id + + +# For those who faced database error, Just uncomment the +# line below and run bot for 1 time & remove that line! + +Permissions.__table__.create(checkfirst=True) +# Permissions.__table__.drop() +Restrictions.__table__.create(checkfirst=True) + +PERM_LOCK = threading.RLock() +RESTR_LOCK = threading.RLock() + + +def init_permissions(chat_id, reset=False): + curr_perm = SESSION.query(Permissions).get(str(chat_id)) + if reset: + SESSION.delete(curr_perm) + SESSION.flush() + perm = Permissions(str(chat_id)) + SESSION.add(perm) + SESSION.commit() + return perm + + +def init_restrictions(chat_id, reset=False): + curr_restr = SESSION.query(Restrictions).get(str(chat_id)) + if reset: + SESSION.delete(curr_restr) + SESSION.flush() + restr = Restrictions(str(chat_id)) + SESSION.add(restr) + SESSION.commit() + return restr + + +def update_lock(chat_id, lock_type, locked): + with PERM_LOCK: + curr_perm = SESSION.query(Permissions).get(str(chat_id)) + if not curr_perm: + curr_perm = init_permissions(chat_id) + + if lock_type == "audio": + curr_perm.audio = locked + elif lock_type == "voice": + curr_perm.voice = locked + elif lock_type == "contact": + curr_perm.contact = locked + elif lock_type == "video": + curr_perm.video = locked + elif lock_type == "document": + curr_perm.document = locked + elif lock_type == "photo": + curr_perm.photo = locked + elif lock_type == "sticker": + curr_perm.sticker = locked + elif lock_type == "gif": + curr_perm.gif = locked + elif lock_type == "url": + curr_perm.url = locked + elif lock_type == "bots": + curr_perm.bots = locked + elif lock_type == "forward": + curr_perm.forward = locked + elif lock_type == "game": + curr_perm.game = locked + elif lock_type == "location": + curr_perm.location = locked + elif lock_type == "rtl": + curr_perm.rtl = locked + elif lock_type == "button": + curr_perm.button = locked + elif lock_type == "egame": + curr_perm.egame = locked + elif lock_type == "inline": + curr_perm.inline = locked + + SESSION.add(curr_perm) + SESSION.commit() + + +def update_restriction(chat_id, restr_type, locked): + with RESTR_LOCK: + curr_restr = SESSION.query(Restrictions).get(str(chat_id)) + if not curr_restr: + curr_restr = init_restrictions(chat_id) + + if restr_type == "messages": + curr_restr.messages = locked + elif restr_type == "media": + curr_restr.media = locked + elif restr_type == "other": + curr_restr.other = locked + elif restr_type == "previews": + curr_restr.preview = locked + elif restr_type == "all": + curr_restr.messages = locked + curr_restr.media = locked + curr_restr.other = locked + curr_restr.preview = locked + SESSION.add(curr_restr) + SESSION.commit() + + +def is_locked(chat_id, lock_type): + curr_perm = SESSION.query(Permissions).get(str(chat_id)) + SESSION.close() + + if not curr_perm: + return False + + if lock_type == "sticker": + return curr_perm.sticker + if lock_type == "photo": + return curr_perm.photo + if lock_type == "audio": + return curr_perm.audio + if lock_type == "voice": + return curr_perm.voice + if lock_type == "contact": + return curr_perm.contact + if lock_type == "video": + return curr_perm.video + if lock_type == "document": + return curr_perm.document + if lock_type == "gif": + return curr_perm.gif + if lock_type == "url": + return curr_perm.url + if lock_type == "bots": + return curr_perm.bots + if lock_type == "forward": + return curr_perm.forward + if lock_type == "game": + return curr_perm.game + if lock_type == "location": + return curr_perm.location + if lock_type == "rtl": + return curr_perm.rtl + if lock_type == "button": + return curr_perm.button + if lock_type == "egame": + return curr_perm.egame + if lock_type == "inline": + return curr_perm.inline + + +def is_restr_locked(chat_id, lock_type): + curr_restr = SESSION.query(Restrictions).get(str(chat_id)) + SESSION.close() + + if not curr_restr: + return False + + if lock_type == "messages": + return curr_restr.messages + if lock_type == "media": + return curr_restr.media + if lock_type == "other": + return curr_restr.other + if lock_type == "previews": + return curr_restr.preview + if lock_type == "all": + return ( + curr_restr.messages + and curr_restr.media + and curr_restr.other + and curr_restr.preview + ) + + +def get_locks(chat_id): + try: + return SESSION.query(Permissions).get(str(chat_id)) + finally: + SESSION.close() + + +def get_restr(chat_id): + try: + return SESSION.query(Restrictions).get(str(chat_id)) + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with PERM_LOCK: + perms = SESSION.query(Permissions).get(str(old_chat_id)) + if perms: + perms.chat_id = str(new_chat_id) + SESSION.commit() + + with RESTR_LOCK: + rest = SESSION.query(Restrictions).get(str(old_chat_id)) + if rest: + rest.chat_id = str(new_chat_id) + SESSION.commit() diff --git a/Database/sql/log_channel_sql.py b/Database/sql/log_channel_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..52b58601f10357b3ebe0b54de4cb126dca12df47 --- /dev/null +++ b/Database/sql/log_channel_sql.py @@ -0,0 +1,180 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading +import typing + +from sqlalchemy import BigInteger, Boolean, Column, String, distinct, func + +from Database.sql import BASE, SESSION + + +class GroupLogs(BASE): + __tablename__ = "log_channels" + chat_id = Column(String(14), primary_key=True) + log_channel = Column(String(14), nullable=False) + + def __init__(self, chat_id, log_channel): + self.chat_id = str(chat_id) + self.log_channel = str(log_channel) + + +class LogChannelSettings(BASE): + __tablename__ = "log_channel_setting" + chat_id = Column(BigInteger, primary_key=True) + log_joins = Column(Boolean, default=True) + log_leave = Column(Boolean, default=True) + log_warn = Column(Boolean, default=True) + log_action = Column(Boolean, default=True) + # log_media = Column(Boolean) + log_report = Column(Boolean, default=True) + + def __init__( + self, + chat_id: int, + log_join: bool, + log_leave: bool, + log_warn: bool, + log_action: bool, + log_report: bool, + ): + self.chat_id = chat_id + self.log_warn = log_warn + self.log_joins = log_join + self.log_leave = log_leave + self.log_report = log_report + self.log_action = log_action + + def toggle_warn(self) -> bool: + self.log_warn = not self.log_warn + SESSION.commit() + return self.log_warn + + def toggle_joins(self) -> bool: + self.log_joins = not self.log_joins + SESSION.commit() + return self.log_joins + + def toggle_leave(self) -> bool: + self.log_leave = not self.log_leave + SESSION.commit() + return self.log_leave + + def toggle_report(self) -> bool: + self.log_report = not self.log_report + SESSION.commit() + return self.log_report + + def toggle_action(self) -> bool: + self.log_action = not self.log_action + SESSION.commit() + return self.log_action + + +GroupLogs.__table__.create(checkfirst=True) +LogChannelSettings.__table__.create(checkfirst=True) + +LOGS_INSERTION_LOCK = threading.RLock() +LOG_SETTING_LOCK = threading.RLock() +CHANNELS = {} + + +def get_chat_setting(chat_id: int) -> typing.Optional[LogChannelSettings]: + with LOG_SETTING_LOCK: + return SESSION.query(LogChannelSettings).get(chat_id) + + +def set_chat_setting(setting: LogChannelSettings): + with LOGS_INSERTION_LOCK: + res: LogChannelSettings = SESSION.query(LogChannelSettings).get(setting.chat_id) + if res: + res.log_warn = setting.log_warn + res.log_action = setting.log_action + res.log_report = setting.log_report + res.log_joins = setting.log_joins + res.log_leave = setting.log_leave + else: + SESSION.add(setting) + SESSION.commit() + + +def set_chat_log_channel(chat_id, log_channel): + with LOGS_INSERTION_LOCK: + res = SESSION.query(GroupLogs).get(str(chat_id)) + if res: + res.log_channel = log_channel + else: + res = GroupLogs(chat_id, log_channel) + SESSION.add(res) + + CHANNELS[str(chat_id)] = log_channel + SESSION.commit() + + +def get_chat_log_channel(chat_id): + return CHANNELS.get(str(chat_id)) + + +def stop_chat_logging(chat_id): + with LOGS_INSERTION_LOCK: + res = SESSION.query(GroupLogs).get(str(chat_id)) + if res: + if str(chat_id) in CHANNELS: + del CHANNELS[str(chat_id)] + + log_channel = res.log_channel + SESSION.delete(res) + SESSION.commit() + return log_channel + + +def num_logchannels(): + try: + return SESSION.query(func.count(distinct(GroupLogs.chat_id))).scalar() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with LOGS_INSERTION_LOCK: + chat = SESSION.query(GroupLogs).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + SESSION.add(chat) + if str(old_chat_id) in CHANNELS: + CHANNELS[str(new_chat_id)] = CHANNELS.get(str(old_chat_id)) + + SESSION.commit() + + +def __load_log_channels(): + global CHANNELS + try: + all_chats = SESSION.query(GroupLogs).all() + CHANNELS = {chat.chat_id: chat.log_channel for chat in all_chats} + finally: + SESSION.close() + + +__load_log_channels() diff --git a/Database/sql/nightmode_sql.py b/Database/sql/nightmode_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..c21fc474f33b5fd6757d9a4817e7dc15cbe14829 --- /dev/null +++ b/Database/sql/nightmode_sql.py @@ -0,0 +1,42 @@ +from sqlalchemy import Column, String + +from Database.sql import BASE, SESSION + + +class Nightmode(BASE): + __tablename__ = "nightmode" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +Nightmode.__table__.create(checkfirst=True) + + +def add_nightmode(chat_id: str): + nightmoddy = Nightmode(str(chat_id)) + SESSION.add(nightmoddy) + SESSION.commit() + + +def rmnightmode(chat_id: str): + rmnightmoddy = SESSION.query(Nightmode).get(str(chat_id)) + if rmnightmoddy: + SESSION.delete(rmnightmoddy) + SESSION.commit() + + +def get_all_chat_id(): + stark = SESSION.query(Nightmode).all() + SESSION.close() + return stark + + +def is_nightmode_indb(chat_id: str): + try: + s__ = SESSION.query(Nightmode).get(str(chat_id)) + if s__: + return str(s__.chat_id) + finally: + SESSION.close() diff --git a/Database/sql/notes_sql.py b/Database/sql/notes_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..629243f7cfa8b1c0e4de384d596712d76f69309d --- /dev/null +++ b/Database/sql/notes_sql.py @@ -0,0 +1,210 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +# Note: chat_id's are stored as strings because the int is too large to be stored in a PSQL database. +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText, distinct, func + +from Database.sql import BASE, SESSION +from Mikobot.plugins.helper_funcs.msg_types import Types + + +class Notes(BASE): + __tablename__ = "notes" + chat_id = Column(String(14), primary_key=True) + name = Column(UnicodeText, primary_key=True) + value = Column(UnicodeText, nullable=False) + file = Column(UnicodeText) + is_reply = Column(Boolean, default=False) + has_buttons = Column(Boolean, default=False) + msgtype = Column(BigInteger, default=Types.BUTTON_TEXT.value) + + def __init__(self, chat_id, name, value, msgtype, file=None): + self.chat_id = str(chat_id) # ensure string + self.name = name + self.value = value + self.msgtype = msgtype + self.file = file + + def __repr__(self): + return "<ɴᴏᴛᴇ %s>" % self.name + + +class Buttons(BASE): + __tablename__ = "note_urls" + id = Column(BigInteger, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + note_name = Column(UnicodeText, primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, note_name, name, url, same_line=False): + self.chat_id = str(chat_id) + self.note_name = note_name + self.name = name + self.url = url + self.same_line = same_line + + +Notes.__table__.create(checkfirst=True) +Buttons.__table__.create(checkfirst=True) + +NOTES_INSERTION_LOCK = threading.RLock() +BUTTONS_INSERTION_LOCK = threading.RLock() + + +def add_note_to_db(chat_id, note_name, note_data, msgtype, buttons=None, file=None): + if not buttons: + buttons = [] + + with NOTES_INSERTION_LOCK: + prev = SESSION.query(Notes).get((str(chat_id), note_name)) + if prev: + with BUTTONS_INSERTION_LOCK: + prev_buttons = ( + SESSION.query(Buttons) + .filter( + Buttons.chat_id == str(chat_id), + Buttons.note_name == note_name, + ) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + SESSION.delete(prev) + note = Notes( + str(chat_id), + note_name, + note_data or "", + msgtype=msgtype.value, + file=file, + ) + SESSION.add(note) + SESSION.commit() + + for b_name, url, same_line in buttons: + add_note_button_to_db(chat_id, note_name, b_name, url, same_line) + + +def get_note(chat_id, note_name): + try: + return ( + SESSION.query(Notes) + .filter(func.lower(Notes.name) == note_name, Notes.chat_id == str(chat_id)) + .first() + ) + finally: + SESSION.close() + + +def rm_note(chat_id, note_name): + with NOTES_INSERTION_LOCK: + note = ( + SESSION.query(Notes) + .filter(func.lower(Notes.name) == note_name, Notes.chat_id == str(chat_id)) + .first() + ) + if note: + with BUTTONS_INSERTION_LOCK: + buttons = ( + SESSION.query(Buttons) + .filter( + Buttons.chat_id == str(chat_id), + Buttons.note_name == note_name, + ) + .all() + ) + for btn in buttons: + SESSION.delete(btn) + + SESSION.delete(note) + SESSION.commit() + return True + SESSION.close() + return False + + +def get_all_chat_notes(chat_id): + try: + return ( + SESSION.query(Notes) + .filter(Notes.chat_id == str(chat_id)) + .order_by(Notes.name.asc()) + .all() + ) + finally: + SESSION.close() + + +def add_note_button_to_db(chat_id, note_name, b_name, url, same_line): + with BUTTONS_INSERTION_LOCK: + button = Buttons(chat_id, note_name, b_name, url, same_line) + SESSION.add(button) + SESSION.commit() + + +def get_buttons(chat_id, note_name): + try: + return ( + SESSION.query(Buttons) + .filter(Buttons.chat_id == str(chat_id), Buttons.note_name == note_name) + .order_by(Buttons.id) + .all() + ) + finally: + SESSION.close() + + +def num_notes(): + try: + return SESSION.query(Notes).count() + finally: + SESSION.close() + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(Notes.chat_id))).scalar() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with NOTES_INSERTION_LOCK: + chat_notes = ( + SESSION.query(Notes).filter(Notes.chat_id == str(old_chat_id)).all() + ) + for note in chat_notes: + note.chat_id = str(new_chat_id) + + with BUTTONS_INSERTION_LOCK: + chat_buttons = ( + SESSION.query(Buttons).filter(Buttons.chat_id == str(old_chat_id)).all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + + SESSION.commit() diff --git a/Database/sql/raid_sql.py b/Database/sql/raid_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..4688248045f6f526465d0d28b03984e2c4fff877 --- /dev/null +++ b/Database/sql/raid_sql.py @@ -0,0 +1,73 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import Column, String + +from Database.sql import BASE, SESSION + + +class RaidChats(BASE): + __tablename__ = "raid_chats" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +RaidChats.__table__.create(checkfirst=True) +INSERTION_LOCK = threading.RLock() + + +def is_raid(chat_id): + try: + chat = SESSION.query(RaidChats).get(str(chat_id)) + return bool(chat) + finally: + SESSION.close() + + +def set_raid(chat_id): + with INSERTION_LOCK: + raidchat = SESSION.query(RaidChats).get(str(chat_id)) + if not raidchat: + raidchat = RaidChats(str(chat_id)) + SESSION.add(raidchat) + SESSION.commit() + + +def rem_raid(chat_id): + with INSERTION_LOCK: + raidchat = SESSION.query(RaidChats).get(str(chat_id)) + if raidchat: + SESSION.delete(raidchat) + SESSION.commit() + + +def get_all_raid_chats(): + try: + return SESSION.query(RaidChats.chat_id).all() + finally: + SESSION.close() diff --git a/Database/sql/remind_sql.py b/Database/sql/remind_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..e77baf153f9cf9f747019f6a8ae6113f6f16cf8f --- /dev/null +++ b/Database/sql/remind_sql.py @@ -0,0 +1,140 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading +import time + +from sqlalchemy import Column, Integer, String, UnicodeText +from sqlalchemy.sql.sqltypes import BigInteger + +from Database.sql import BASE, SESSION + + +class Reminds(BASE): + __tablename__ = "reminds" + chat_id = Column(String(14), primary_key=True) + time_seconds = Column(Integer, primary_key=True) + remind_message = Column(UnicodeText, default="") + user_id = Column(BigInteger, default=0) + + def __init__(self, chat_id, time_seconds): + self.chat_id = str(chat_id) + self.time_seconds = int(time_seconds) + + def __repr__(self): + return "<ʀᴇᴍɪɴᴅ ɪɴ {} ғᴏʀ ᴛɪᴍᴇ {}>".format( + self.chat_id, + self.time_seconds, + ) + + +# Reminds.__table__.drop() +Reminds.__table__.create(checkfirst=True) + +INSERTION_LOCK = threading.RLock() + +REMINDERS = {} + + +def set_remind(chat_id, time_sec, remind_message, user_id): + with INSERTION_LOCK: + reminds = SESSION.query(Reminds).get((str(chat_id), time_sec)) + if not reminds: + reminds = Reminds(chat_id, time_sec) + reminds.remind_message = remind_message + reminds.user_id = user_id + SESSION.add(reminds) + SESSION.commit() + if not time_sec in REMINDERS: + REMINDERS[time_sec] = [] + REMINDERS[time_sec].append( + {"chat_id": str(chat_id), "message": remind_message, "user_id": user_id} + ) + + +def rem_remind(chat_id, time_sec, remind_message, user_id): + with INSERTION_LOCK: + reminds = SESSION.query(Reminds).get((str(chat_id), time_sec)) + if reminds: + SESSION.delete(reminds) + SESSION.commit() + REMINDERS[time_sec].remove( + {"chat_id": str(chat_id), "message": remind_message, "user_id": user_id} + ) + return True + SESSION.close() + return False + + +def get_remind_in_chat(chat_id, timestamp): + return ( + SESSION.query(Reminds) + .filter(Reminds.chat_id == str(chat_id), Reminds.time_seconds == timestamp) + .first() + ) + + +def num_reminds_in_chat(chat_id): + return SESSION.query(Reminds).filter(Reminds.chat_id == str(chat_id)).count() + + +def get_reminds_in_chat(chat_id): + try: + return ( + SESSION.query(Reminds) + .filter(Reminds.chat_id == str(chat_id)) + .order_by(Reminds.time_seconds.asc()) + .all() + ) + finally: + SESSION.close() + + +def __get_all_reminds(): + try: + chats = SESSION.query(Reminds).all() + for chat in chats: + if (chat.time_seconds <= round(time.time())) or chat.user_id == 0: + try: + rem_remind( + chat.chat_id, + chat.time_seconds, + chat.remind_message, + chat.user_id, + ) + except: + pass + continue + REMINDERS[chat.time_seconds] = [ + { + "chat_id": chat.chat_id, + "message": chat.remind_message, + "user_id": chat.user_id, + } + ] + finally: + SESSION.close() + + +__get_all_reminds() diff --git a/Database/sql/reporting_sql.py b/Database/sql/reporting_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..48c42d4ee70002af67ade04c95ede1bd04af4f0c --- /dev/null +++ b/Database/sql/reporting_sql.py @@ -0,0 +1,115 @@ +""" +MIT License + +Copyright (c) 2022 Arsh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading +from typing import Union + +from sqlalchemy import BigInteger, Boolean, Column, String + +from Database.sql import BASE, SESSION + + +class ReportingUserSettings(BASE): + __tablename__ = "user_report_settings" + user_id = Column(BigInteger, primary_key=True) + should_report = Column(Boolean, default=True) + + def __init__(self, user_id): + self.user_id = user_id + + def __repr__(self): + return "<ᴜsᴇʀ ʀᴇᴘᴏʀᴛ sᴇᴛᴛɪɴɢs ({})>".format(self.user_id) + + +class ReportingChatSettings(BASE): + __tablename__ = "chat_report_settings" + chat_id = Column(String(14), primary_key=True) + should_report = Column(Boolean, default=True) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) + + def __repr__(self): + return "<ᴄʜᴀᴛ ʀᴇᴘᴏʀᴛ sᴇᴛᴛɪɴɢs ({})>".format(self.chat_id) + + +ReportingUserSettings.__table__.create(checkfirst=True) +ReportingChatSettings.__table__.create(checkfirst=True) + +CHAT_LOCK = threading.RLock() +USER_LOCK = threading.RLock() + + +def chat_should_report(chat_id: Union[str, int]) -> bool: + try: + chat_setting = SESSION.query(ReportingChatSettings).get(str(chat_id)) + if chat_setting: + return chat_setting.should_report + return False + finally: + SESSION.close() + + +def user_should_report(user_id: int) -> bool: + try: + user_setting = SESSION.query(ReportingUserSettings).get(user_id) + if user_setting: + return user_setting.should_report + return True + finally: + SESSION.close() + + +def set_chat_setting(chat_id: Union[int, str], setting: bool): + with CHAT_LOCK: + chat_setting = SESSION.query(ReportingChatSettings).get(str(chat_id)) + if not chat_setting: + chat_setting = ReportingChatSettings(chat_id) + + chat_setting.should_report = setting + SESSION.add(chat_setting) + SESSION.commit() + + +def set_user_setting(user_id: int, setting: bool): + with USER_LOCK: + user_setting = SESSION.query(ReportingUserSettings).get(user_id) + if not user_setting: + user_setting = ReportingUserSettings(user_id) + + user_setting.should_report = setting + SESSION.add(user_setting) + SESSION.commit() + + +def migrate_chat(old_chat_id, new_chat_id): + with CHAT_LOCK: + chat_notes = ( + SESSION.query(ReportingChatSettings) + .filter(ReportingChatSettings.chat_id == str(old_chat_id)) + .all() + ) + for note in chat_notes: + note.chat_id = str(new_chat_id) + SESSION.commit() diff --git a/Database/sql/rules_sql.py b/Database/sql/rules_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..a9591f0b2b05c278e41d41de19879ed598b0ed45 --- /dev/null +++ b/Database/sql/rules_sql.py @@ -0,0 +1,58 @@ +import threading + +from sqlalchemy import Column, String, UnicodeText, distinct, func + +from Database.sql import BASE, SESSION + + +class Rules(BASE): + __tablename__ = "rules" + chat_id = Column(String(14), primary_key=True) + rules = Column(UnicodeText, default="") + + def __init__(self, chat_id): + self.chat_id = chat_id + + def __repr__(self): + return "".format(self.chat_id, self.rules) + + +Rules.__table__.create(checkfirst=True) + +INSERTION_LOCK = threading.RLock() + + +def set_rules(chat_id, rules_text): + with INSERTION_LOCK: + rules = SESSION.query(Rules).get(str(chat_id)) + if not rules: + rules = Rules(str(chat_id)) + rules.rules = rules_text + + SESSION.add(rules) + SESSION.commit() + + +def get_rules(chat_id): + rules = SESSION.query(Rules).get(str(chat_id)) + ret = "" + if rules: + ret = rules.rules + + SESSION.close() + return ret + + +def num_chats(): + try: + return SESSION.query(func.count(distinct(Rules.chat_id))).scalar() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Rules).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + SESSION.commit() diff --git a/Database/sql/userinfo_sql.py b/Database/sql/userinfo_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..d5151e95ffab55a171c7535387faecbbd9dc93b0 --- /dev/null +++ b/Database/sql/userinfo_sql.py @@ -0,0 +1,100 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏᴏ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Column, UnicodeText + +from Database.sql import BASE, SESSION + + +class UserInfo(BASE): + __tablename__ = "userinfo" + user_id = Column(BigInteger, primary_key=True) + info = Column(UnicodeText) + + def __init__(self, user_id, info): + self.user_id = user_id + self.info = info + + def __repr__(self): + return "<ᴜsᴇʀ ɪɴғᴏ %d>" % self.user_id + + +class UserBio(BASE): + __tablename__ = "userbio" + user_id = Column(BigInteger, primary_key=True) + bio = Column(UnicodeText) + + def __init__(self, user_id, bio): + self.user_id = user_id + self.bio = bio + + def __repr__(self): + return "<ᴜsᴇʀ ɪɴғᴏ %d>" % self.user_id + + +UserInfo.__table__.create(checkfirst=True) +UserBio.__table__.create(checkfirst=True) + +INSERTION_LOCK = threading.RLock() + + +def get_user_me_info(user_id): + userinfo = SESSION.query(UserInfo).get(user_id) + SESSION.close() + if userinfo: + return userinfo.info + return None + + +def set_user_me_info(user_id, info): + with INSERTION_LOCK: + userinfo = SESSION.query(UserInfo).get(user_id) + if userinfo: + userinfo.info = info + else: + userinfo = UserInfo(user_id, info) + SESSION.add(userinfo) + SESSION.commit() + + +def get_user_bio(user_id): + userbio = SESSION.query(UserBio).get(user_id) + SESSION.close() + if userbio: + return userbio.bio + return None + + +def set_user_bio(user_id, bio): + with INSERTION_LOCK: + userbio = SESSION.query(UserBio).get(user_id) + if userbio: + userbio.bio = bio + else: + userbio = UserBio(user_id, bio) + + SESSION.add(userbio) + SESSION.commit() diff --git a/Database/sql/users_sql.py b/Database/sql/users_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..6307ab94be58117aa65cd22a2a5e485b807153ce --- /dev/null +++ b/Database/sql/users_sql.py @@ -0,0 +1,258 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import ( + BigInteger, + Column, + ForeignKey, + String, + UnicodeText, + UniqueConstraint, + func, +) + +from Database.sql import BASE, SESSION +from Mikobot import dispatcher + + +class Users(BASE): + __tablename__ = "users" + user_id = Column(BigInteger, primary_key=True) + username = Column(UnicodeText) + + def __init__(self, user_id, username=None): + self.user_id = user_id + self.username = username + + def __repr__(self): + return "<ᴜsᴇʀ {} ({})>".format(self.username, self.user_id) + + +class Chats(BASE): + __tablename__ = "chats" + chat_id = Column(String(14), primary_key=True) + chat_name = Column(UnicodeText, nullable=False) + + def __init__(self, chat_id, chat_name): + self.chat_id = str(chat_id) + self.chat_name = chat_name + + def __repr__(self): + return "<ᴄʜᴀᴛ {} ({})>".format(self.chat_name, self.chat_id) + + +class ChatMembers(BASE): + __tablename__ = "chat_members" + priv_chat_id = Column(BigInteger, primary_key=True) + # NOTE: Use dual primary key instead of private primary key? + chat = Column( + String(14), + ForeignKey("chats.chat_id", onupdate="CASCADE", ondelete="CASCADE"), + nullable=False, + ) + user = Column( + BigInteger, + ForeignKey("users.user_id", onupdate="CASCADE", ondelete="CASCADE"), + nullable=False, + ) + __table_args__ = (UniqueConstraint("chat", "user", name="_chat_members_uc"),) + + def __init__(self, chat, user): + self.chat = chat + self.user = user + + def __repr__(self): + return "<ᴄʜᴀᴛ ᴜsᴇʀ {} ({}) ɪɴ ᴄʜᴀᴛ {} ({})>".format( + self.user.username, + self.user.user_id, + self.chat.chat_name, + self.chat.chat_id, + ) + + +Users.__table__.create(checkfirst=True) +Chats.__table__.create(checkfirst=True) +ChatMembers.__table__.create(checkfirst=True) + +INSERTION_LOCK = threading.RLock() + + +def ensure_bot_in_db(): + with INSERTION_LOCK: + bot = Users(dispatcher.bot.id, dispatcher.bot.username) + SESSION.merge(bot) + SESSION.commit() + + +def update_user(user_id, username, chat_id=None, chat_name=None): + with INSERTION_LOCK: + user = SESSION.query(Users).get(user_id) + if not user: + user = Users(user_id, username) + SESSION.add(user) + SESSION.flush() + else: + user.username = username + + if not chat_id or not chat_name: + SESSION.commit() + return + + chat = SESSION.query(Chats).get(str(chat_id)) + if not chat: + chat = Chats(str(chat_id), chat_name) + SESSION.add(chat) + SESSION.flush() + + else: + chat.chat_name = chat_name + + member = ( + SESSION.query(ChatMembers) + .filter(ChatMembers.chat == chat.chat_id, ChatMembers.user == user.user_id) + .first() + ) + if not member: + chat_member = ChatMembers(chat.chat_id, user.user_id) + SESSION.add(chat_member) + + SESSION.commit() + + +def get_userid_by_name(username): + try: + return ( + SESSION.query(Users) + .filter(func.lower(Users.username) == username.lower()) + .all() + ) + finally: + SESSION.close() + + +def get_name_by_userid(user_id): + try: + return SESSION.query(Users).get(Users.user_id == int(user_id)).first() + finally: + SESSION.close() + + +def get_chat_members(chat_id): + try: + return SESSION.query(ChatMembers).filter(ChatMembers.chat == str(chat_id)).all() + finally: + SESSION.close() + + +def get_all_chats(): + try: + return SESSION.query(Chats).all() + finally: + SESSION.close() + + +def get_all_users(): + try: + return SESSION.query(Users).all() + finally: + SESSION.close() + + +def get_user_num_chats(user_id): + try: + return ( + SESSION.query(ChatMembers).filter(ChatMembers.user == int(user_id)).count() + ) + finally: + SESSION.close() + + +def get_user_com_chats(user_id): + try: + chat_members = ( + SESSION.query(ChatMembers).filter(ChatMembers.user == int(user_id)).all() + ) + return [i.chat for i in chat_members] + finally: + SESSION.close() + + +def num_chats(): + try: + return SESSION.query(Chats).count() + finally: + SESSION.close() + + +def num_users(): + try: + return SESSION.query(Users).count() + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Chats).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + SESSION.commit() + + chat_members = ( + SESSION.query(ChatMembers) + .filter(ChatMembers.chat == str(old_chat_id)) + .all() + ) + for member in chat_members: + member.chat = str(new_chat_id) + SESSION.commit() + + +ensure_bot_in_db() + + +def del_user(user_id): + with INSERTION_LOCK: + curr = SESSION.query(Users).get(user_id) + if curr: + SESSION.delete(curr) + SESSION.commit() + return True + + ChatMembers.query.filter(ChatMembers.user == user_id).delete() + SESSION.commit() + SESSION.close() + return False + + +def rem_chat(chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Chats).get(str(chat_id)) + if chat: + SESSION.delete(chat) + SESSION.commit() + else: + SESSION.close() diff --git a/Database/sql/warns_sql.py b/Database/sql/warns_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..610cdfc3791cdc49ab7aacdd105552f5857ed566 --- /dev/null +++ b/Database/sql/warns_sql.py @@ -0,0 +1,341 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import threading + +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText, distinct, func +from sqlalchemy.dialects import postgresql + +from Database.sql import BASE, SESSION + + +class Warns(BASE): + __tablename__ = "warns" + + user_id = Column(BigInteger, primary_key=True) + chat_id = Column(String(14), primary_key=True) + num_warns = Column(BigInteger, default=0) + reasons = Column(postgresql.ARRAY(UnicodeText)) + + def __init__(self, user_id, chat_id): + self.user_id = user_id + self.chat_id = str(chat_id) + self.num_warns = 0 + self.reasons = [] + + def __repr__(self): + return "<{} ᴡᴀʀɴs ғᴏʀ {} ɪɴ {} ғᴏʀ ʀᴇᴀsᴏɴs {}>".format( + self.num_warns, + self.user_id, + self.chat_id, + self.reasons, + ) + + +class WarnFilters(BASE): + __tablename__ = "warn_filters" + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True, nullable=False) + reply = Column(UnicodeText, nullable=False) + + def __init__(self, chat_id, keyword, reply): + self.chat_id = str(chat_id) # ensure string + self.keyword = keyword + self.reply = reply + + def __repr__(self): + return "<ᴘᴇʀᴍɪssɪᴏɴs ғᴏʀ %s>" % self.chat_id + + def __eq__(self, other): + return bool( + isinstance(other, WarnFilters) + and self.chat_id == other.chat_id + and self.keyword == other.keyword, + ) + + +class WarnSettings(BASE): + __tablename__ = "warn_settings" + chat_id = Column(String(14), primary_key=True) + warn_limit = Column(BigInteger, default=3) + soft_warn = Column(Boolean, default=False) + + def __init__(self, chat_id, warn_limit=3, soft_warn=False): + self.chat_id = str(chat_id) + self.warn_limit = warn_limit + self.soft_warn = soft_warn + + def __repr__(self): + return "<{} ʜᴀs {} ᴘᴏssɪʙʟᴇ ᴡᴀʀɴs.>".format(self.chat_id, self.warn_limit) + + +Warns.__table__.create(checkfirst=True) +WarnFilters.__table__.create(checkfirst=True) +WarnSettings.__table__.create(checkfirst=True) + +WARN_INSERTION_LOCK = threading.RLock() +WARN_FILTER_INSERTION_LOCK = threading.RLock() +WARN_SETTINGS_LOCK = threading.RLock() + +WARN_FILTERS = {} + + +def warn_user(user_id, chat_id, reason=None): + with WARN_INSERTION_LOCK: + warned_user = SESSION.query(Warns).get((user_id, str(chat_id))) + if not warned_user: + warned_user = Warns(user_id, str(chat_id)) + + warned_user.num_warns += 1 + if reason: + warned_user.reasons = warned_user.reasons + [ + reason, + ] # TODO:: double check this wizardry + + reasons = warned_user.reasons + num = warned_user.num_warns + + SESSION.add(warned_user) + SESSION.commit() + + return num, reasons + + +def remove_warn(user_id, chat_id): + with WARN_INSERTION_LOCK: + removed = False + warned_user = SESSION.query(Warns).get((user_id, str(chat_id))) + + if warned_user and warned_user.num_warns > 0: + warned_user.num_warns -= 1 + warned_user.reasons = warned_user.reasons[:-1] + SESSION.add(warned_user) + SESSION.commit() + removed = True + + SESSION.close() + return removed + + +def reset_warns(user_id, chat_id): + with WARN_INSERTION_LOCK: + warned_user = SESSION.query(Warns).get((user_id, str(chat_id))) + if warned_user: + warned_user.num_warns = 0 + warned_user.reasons = [] + + SESSION.add(warned_user) + SESSION.commit() + SESSION.close() + + +def get_warns(user_id, chat_id): + try: + user = SESSION.query(Warns).get((user_id, str(chat_id))) + if not user: + return None + reasons = user.reasons + num = user.num_warns + return num, reasons + finally: + SESSION.close() + + +def add_warn_filter(chat_id, keyword, reply): + with WARN_FILTER_INSERTION_LOCK: + warn_filt = WarnFilters(str(chat_id), keyword, reply) + + if keyword not in WARN_FILTERS.get(str(chat_id), []): + WARN_FILTERS[str(chat_id)] = sorted( + WARN_FILTERS.get(str(chat_id), []) + [keyword], + key=lambda x: (-len(x), x), + ) + + SESSION.merge(warn_filt) # merge to avoid duplicate key issues + SESSION.commit() + + +def remove_warn_filter(chat_id, keyword): + with WARN_FILTER_INSERTION_LOCK: + warn_filt = SESSION.query(WarnFilters).get((str(chat_id), keyword)) + if warn_filt: + if keyword in WARN_FILTERS.get(str(chat_id), []): # sanity check + WARN_FILTERS.get(str(chat_id), []).remove(keyword) + + SESSION.delete(warn_filt) + SESSION.commit() + return True + SESSION.close() + return False + + +def get_chat_warn_triggers(chat_id): + return WARN_FILTERS.get(str(chat_id), set()) + + +def get_chat_warn_filters(chat_id): + try: + return ( + SESSION.query(WarnFilters).filter(WarnFilters.chat_id == str(chat_id)).all() + ) + finally: + SESSION.close() + + +def get_warn_filter(chat_id, keyword): + try: + return SESSION.query(WarnFilters).get((str(chat_id), keyword)) + finally: + SESSION.close() + + +def set_warn_limit(chat_id, warn_limit): + with WARN_SETTINGS_LOCK: + curr_setting = SESSION.query(WarnSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = WarnSettings(chat_id, warn_limit=warn_limit) + + curr_setting.warn_limit = warn_limit + + SESSION.add(curr_setting) + SESSION.commit() + + +def set_warn_strength(chat_id, soft_warn): + with WARN_SETTINGS_LOCK: + curr_setting = SESSION.query(WarnSettings).get(str(chat_id)) + if not curr_setting: + curr_setting = WarnSettings(chat_id, soft_warn=soft_warn) + + curr_setting.soft_warn = soft_warn + + SESSION.add(curr_setting) + SESSION.commit() + + +def get_warn_setting(chat_id): + try: + setting = SESSION.query(WarnSettings).get(str(chat_id)) + if setting: + return setting.warn_limit, setting.soft_warn + return 3, False + + finally: + SESSION.close() + + +def num_warns(): + try: + return SESSION.query(func.sum(Warns.num_warns)).scalar() or 0 + finally: + SESSION.close() + + +def num_warn_chats(): + try: + return SESSION.query(func.count(distinct(Warns.chat_id))).scalar() + finally: + SESSION.close() + + +def num_warn_filters(): + try: + return SESSION.query(WarnFilters).count() + finally: + SESSION.close() + + +def num_warn_chat_filters(chat_id): + try: + return ( + SESSION.query(WarnFilters.chat_id) + .filter(WarnFilters.chat_id == str(chat_id)) + .count() + ) + finally: + SESSION.close() + + +def num_warn_filter_chats(): + try: + return SESSION.query(func.count(distinct(WarnFilters.chat_id))).scalar() + finally: + SESSION.close() + + +def __load_chat_warn_filters(): + global WARN_FILTERS + try: + chats = SESSION.query(WarnFilters.chat_id).distinct().all() + for (chat_id,) in chats: # remove tuple by ( ,) + WARN_FILTERS[chat_id] = [] + + all_filters = SESSION.query(WarnFilters).all() + for x in all_filters: + WARN_FILTERS[x.chat_id] += [x.keyword] + + WARN_FILTERS = { + x: sorted(set(y), key=lambda i: (-len(i), i)) + for x, y in WARN_FILTERS.items() + } + + finally: + SESSION.close() + + +def migrate_chat(old_chat_id, new_chat_id): + with WARN_INSERTION_LOCK: + chat_notes = ( + SESSION.query(Warns).filter(Warns.chat_id == str(old_chat_id)).all() + ) + for note in chat_notes: + note.chat_id = str(new_chat_id) + SESSION.commit() + + with WARN_FILTER_INSERTION_LOCK: + chat_filters = ( + SESSION.query(WarnFilters) + .filter(WarnFilters.chat_id == str(old_chat_id)) + .all() + ) + for filt in chat_filters: + filt.chat_id = str(new_chat_id) + SESSION.commit() + old_warn_filt = WARN_FILTERS.get(str(old_chat_id)) + if old_warn_filt is not None: + WARN_FILTERS[str(new_chat_id)] = old_warn_filt + del WARN_FILTERS[str(old_chat_id)] + + with WARN_SETTINGS_LOCK: + chat_settings = ( + SESSION.query(WarnSettings) + .filter(WarnSettings.chat_id == str(old_chat_id)) + .all() + ) + for setting in chat_settings: + setting.chat_id = str(new_chat_id) + SESSION.commit() + + +__load_chat_warn_filters() diff --git a/Database/sql/welcome_sql.py b/Database/sql/welcome_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..1b73f046a9e81a40de4662f90aac0e7ef9d259bd --- /dev/null +++ b/Database/sql/welcome_sql.py @@ -0,0 +1,507 @@ +""" +MIT License + +Copyright (c) 2022 Aʙɪsʜɴᴏɪ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import random +import threading +from typing import Union + +from sqlalchemy import BigInteger, Boolean, Column, Integer, String, UnicodeText + +from Database.sql import BASE, SESSION +from Mikobot.plugins.helper_funcs.msg_types import Types + +DEFAULT_WELCOME = "ʜᴇʏ {first}, ʜᴏᴡ ᴀʀᴇ ʏᴏᴜ?" +DEFAULT_GOODBYE = "ɴɪᴄᴇ ᴋɴᴏᴡɪɴɢ ʏᴀ!" + +DEFAULT_WELCOME_MESSAGES = [ + "{first} ɪs ʜᴇʀᴇ!", # Discord welcome messages copied + "ʀᴇᴀᴅʏ ᴘʟᴀʏᴇʀ {first}", + "ᴡᴇʟᴄᴏᴍᴇ ʙʀᴏ {first}", +] + +DEFAULT_GOODBYE_MESSAGES = [ + "{first} ᴡɪʟʟ ʙᴇ ᴍɪssᴇᴅ.", + "{first} ᴡʜᴇɴ ʙᴀᴄᴋ ?.", +] + + +class Welcome(BASE): + __tablename__ = "welcome_pref" + chat_id = Column(String(14), primary_key=True) + should_welcome = Column(Boolean, default=True) + should_goodbye = Column(Boolean, default=True) + custom_content = Column(UnicodeText, default=None) + + custom_welcome = Column( + UnicodeText, default=random.choice(DEFAULT_WELCOME_MESSAGES) + ) + welcome_type = Column(Integer, default=Types.TEXT.value) + + custom_leave = Column(UnicodeText, default=random.choice(DEFAULT_GOODBYE_MESSAGES)) + leave_type = Column(Integer, default=Types.TEXT.value) + + clean_welcome = Column(BigInteger) + + def __init__(self, chat_id, should_welcome=True, should_goodbye=True): + self.chat_id = chat_id + self.should_welcome = should_welcome + self.should_goodbye = should_goodbye + + def __repr__(self): + return "<ᴄʜᴀᴛ {} sʜᴏᴜʟᴅ sʜᴏᴜʟᴅ ɴᴇᴡ ᴜsᴇʀs: {}>".format( + self.chat_id, self.should_welcome + ) + + +class WelcomeButtons(BASE): + __tablename__ = "welcome_urls" + id = Column(Integer, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, name, url, same_line=False): + self.chat_id = str(chat_id) + self.name = name + self.url = url + self.same_line = same_line + + +class GoodbyeButtons(BASE): + __tablename__ = "leave_urls" + id = Column(Integer, primary_key=True, autoincrement=True) + chat_id = Column(String(14), primary_key=True) + name = Column(UnicodeText, nullable=False) + url = Column(UnicodeText, nullable=False) + same_line = Column(Boolean, default=False) + + def __init__(self, chat_id, name, url, same_line=False): + self.chat_id = str(chat_id) + self.name = name + self.url = url + self.same_line = same_line + + +class WelcomeMute(BASE): + __tablename__ = "welcome_mutes" + chat_id = Column(String(14), primary_key=True) + welcomemutes = Column(UnicodeText, default=False) + + def __init__(self, chat_id, welcomemutes): + self.chat_id = str(chat_id) # ensure string + self.welcomemutes = welcomemutes + + +class WelcomeMuteUsers(BASE): + __tablename__ = "human_checks" + user_id = Column(BigInteger, primary_key=True) + chat_id = Column(String(14), primary_key=True) + human_check = Column(Boolean) + + def __init__(self, user_id, chat_id, human_check): + self.user_id = user_id # ensure string + self.chat_id = str(chat_id) + self.human_check = human_check + + +class CleanServiceSetting(BASE): + __tablename__ = "clean_service" + chat_id = Column(String(14), primary_key=True) + clean_service = Column(Boolean, default=True) + + def __init__(self, chat_id): + self.chat_id = str(chat_id) + + def __repr__(self): + return "<ᴄʜᴀᴛ ᴜsᴇᴅ ᴄʟᴇᴀɴ sᴇʀᴠɪᴄᴇ ({})>".format(self.chat_id) + + +class RaidMode(BASE): + __tablename__ = "raid_mode" + chat_id = Column(String(14), primary_key=True) + status = Column(Boolean, default=False) + time = Column(Integer, default=21600) + acttime = Column(Integer, default=3600) + # permanent = Column(Boolean, default=False) + + def __init__(self, chat_id, status, time, acttime): + self.chat_id = str(chat_id) + self.status = status + self.time = time + self.acttime = acttime + # self.permanent = permanent + + +Welcome.__table__.create(checkfirst=True) +WelcomeButtons.__table__.create(checkfirst=True) +GoodbyeButtons.__table__.create(checkfirst=True) +WelcomeMute.__table__.create(checkfirst=True) +WelcomeMuteUsers.__table__.create(checkfirst=True) +CleanServiceSetting.__table__.create(checkfirst=True) +RaidMode.__table__.create(checkfirst=True) + +INSERTION_LOCK = threading.RLock() +WELC_BTN_LOCK = threading.RLock() +LEAVE_BTN_LOCK = threading.RLock() +WM_LOCK = threading.RLock() +CS_LOCK = threading.RLock() +RAID_LOCK = threading.RLock() + + +def welcome_mutes(chat_id): + try: + welcomemutes = SESSION.query(WelcomeMute).get(str(chat_id)) + if welcomemutes: + return welcomemutes.welcomemutes + return False + finally: + SESSION.close() + + +def set_welcome_mutes(chat_id, welcomemutes): + with WM_LOCK: + prev = SESSION.query(WelcomeMute).get((str(chat_id))) + if prev: + SESSION.delete(prev) + welcome_m = WelcomeMute(str(chat_id), welcomemutes) + SESSION.add(welcome_m) + SESSION.commit() + + +def set_human_checks(user_id, chat_id): + with INSERTION_LOCK: + human_check = SESSION.query(WelcomeMuteUsers).get((user_id, str(chat_id))) + if not human_check: + human_check = WelcomeMuteUsers(user_id, str(chat_id), True) + + else: + human_check.human_check = True + + SESSION.add(human_check) + SESSION.commit() + + return human_check + + +def get_human_checks(user_id, chat_id): + try: + human_check = SESSION.query(WelcomeMuteUsers).get((user_id, str(chat_id))) + if not human_check: + return None + human_check = human_check.human_check + return human_check + finally: + SESSION.close() + + +def get_welc_mutes_pref(chat_id): + welcomemutes = SESSION.query(WelcomeMute).get(str(chat_id)) + SESSION.close() + + if welcomemutes: + return welcomemutes.welcomemutes + + return False + + +def get_welc_pref(chat_id): + welc = SESSION.query(Welcome).get(str(chat_id)) + SESSION.close() + if welc: + return ( + welc.should_welcome, + welc.custom_welcome, + welc.custom_content, + welc.welcome_type, + ) + + else: + # Welcome by default. + return True, DEFAULT_WELCOME, None, Types.TEXT + + +def get_gdbye_pref(chat_id): + welc = SESSION.query(Welcome).get(str(chat_id)) + SESSION.close() + if welc: + return welc.should_goodbye, welc.custom_leave, welc.leave_type + else: + # Welcome by default. + return True, DEFAULT_GOODBYE, Types.TEXT + + +def set_clean_welcome(chat_id, clean_welcome): + with INSERTION_LOCK: + curr = SESSION.query(Welcome).get(str(chat_id)) + if not curr: + curr = Welcome(str(chat_id)) + + curr.clean_welcome = int(clean_welcome) + + SESSION.add(curr) + SESSION.commit() + + +def get_clean_pref(chat_id): + welc = SESSION.query(Welcome).get(str(chat_id)) + SESSION.close() + + if welc: + return welc.clean_welcome + + return False + + +def set_welc_preference(chat_id, should_welcome): + with INSERTION_LOCK: + curr = SESSION.query(Welcome).get(str(chat_id)) + if not curr: + curr = Welcome(str(chat_id), should_welcome=should_welcome) + else: + curr.should_welcome = should_welcome + + SESSION.add(curr) + SESSION.commit() + + +def set_gdbye_preference(chat_id, should_goodbye): + with INSERTION_LOCK: + curr = SESSION.query(Welcome).get(str(chat_id)) + if not curr: + curr = Welcome(str(chat_id), should_goodbye=should_goodbye) + else: + curr.should_goodbye = should_goodbye + + SESSION.add(curr) + SESSION.commit() + + +def set_custom_welcome( + chat_id, custom_content, custom_welcome, welcome_type, buttons=None +): + if buttons is None: + buttons = [] + + with INSERTION_LOCK: + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + if not welcome_settings: + welcome_settings = Welcome(str(chat_id), True) + + if custom_welcome or custom_content: + welcome_settings.custom_content = custom_content + welcome_settings.custom_welcome = custom_welcome + welcome_settings.welcome_type = welcome_type.value + + else: + welcome_settings.custom_welcome = DEFAULT_WELCOME + welcome_settings.welcome_type = Types.TEXT.value + + SESSION.add(welcome_settings) + + with WELC_BTN_LOCK: + prev_buttons = ( + SESSION.query(WelcomeButtons) + .filter(WelcomeButtons.chat_id == str(chat_id)) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + + for b_name, url, same_line in buttons: + button = WelcomeButtons(chat_id, b_name, url, same_line) + SESSION.add(button) + + SESSION.commit() + + +def get_custom_welcome(chat_id): + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + ret = DEFAULT_WELCOME + if welcome_settings and welcome_settings.custom_welcome: + ret = welcome_settings.custom_welcome + + SESSION.close() + return ret + + +def set_custom_gdbye(chat_id, custom_goodbye, goodbye_type, buttons=None): + if buttons is None: + buttons = [] + + with INSERTION_LOCK: + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + if not welcome_settings: + welcome_settings = Welcome(str(chat_id), True) + + if custom_goodbye: + welcome_settings.custom_leave = custom_goodbye + welcome_settings.leave_type = goodbye_type.value + + else: + welcome_settings.custom_leave = DEFAULT_GOODBYE + welcome_settings.leave_type = Types.TEXT.value + + SESSION.add(welcome_settings) + + with LEAVE_BTN_LOCK: + prev_buttons = ( + SESSION.query(GoodbyeButtons) + .filter(GoodbyeButtons.chat_id == str(chat_id)) + .all() + ) + for btn in prev_buttons: + SESSION.delete(btn) + + for b_name, url, same_line in buttons: + button = GoodbyeButtons(chat_id, b_name, url, same_line) + SESSION.add(button) + + SESSION.commit() + + +def get_custom_gdbye(chat_id): + welcome_settings = SESSION.query(Welcome).get(str(chat_id)) + ret = DEFAULT_GOODBYE + if welcome_settings and welcome_settings.custom_leave: + ret = welcome_settings.custom_leave + + SESSION.close() + return ret + + +def get_welc_buttons(chat_id): + try: + return ( + SESSION.query(WelcomeButtons) + .filter(WelcomeButtons.chat_id == str(chat_id)) + .order_by(WelcomeButtons.id) + .all() + ) + finally: + SESSION.close() + + +def get_gdbye_buttons(chat_id): + try: + return ( + SESSION.query(GoodbyeButtons) + .filter(GoodbyeButtons.chat_id == str(chat_id)) + .order_by(GoodbyeButtons.id) + .all() + ) + finally: + SESSION.close() + + +def clean_service(chat_id: Union[str, int]) -> bool: + try: + chat_setting = SESSION.query(CleanServiceSetting).get(str(chat_id)) + if chat_setting: + return chat_setting.clean_service + return False + finally: + SESSION.close() + + +def set_clean_service(chat_id: Union[int, str], setting: bool): + with CS_LOCK: + chat_setting = SESSION.query(CleanServiceSetting).get(str(chat_id)) + if not chat_setting: + chat_setting = CleanServiceSetting(chat_id) + + chat_setting.clean_service = setting + SESSION.add(chat_setting) + SESSION.commit() + + +def migrate_chat(old_chat_id, new_chat_id): + with INSERTION_LOCK: + chat = SESSION.query(Welcome).get(str(old_chat_id)) + if chat: + chat.chat_id = str(new_chat_id) + + with WELC_BTN_LOCK: + chat_buttons = ( + SESSION.query(WelcomeButtons) + .filter(WelcomeButtons.chat_id == str(old_chat_id)) + .all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + + with LEAVE_BTN_LOCK: + chat_buttons = ( + SESSION.query(GoodbyeButtons) + .filter(GoodbyeButtons.chat_id == str(old_chat_id)) + .all() + ) + for btn in chat_buttons: + btn.chat_id = str(new_chat_id) + + SESSION.commit() + + +def getRaidStatus(chat_id): + try: + if stat := SESSION.query(RaidMode).get(str(chat_id)): + return stat.status, stat.time, stat.acttime + return False, 21600, 3600 # default + finally: + SESSION.close() + + +def setRaidStatus(chat_id, status, time=21600, acttime=3600): + with RAID_LOCK: + if prevObj := SESSION.query(RaidMode).get(str(chat_id)): + SESSION.delete(prevObj) + newObj = RaidMode(str(chat_id), status, time, acttime) + SESSION.add(newObj) + SESSION.commit() + + +def toggleRaidStatus(chat_id): + newObj = True + with RAID_LOCK: + prevObj = SESSION.query(RaidMode).get(str(chat_id)) + if prevObj: + newObj = not prevObj.status + stat = RaidMode( + str(chat_id), newObj, prevObj.time or 21600, prevObj.acttime or 3600 + ) + SESSION.add(stat) + SESSION.commit() + return newObj + + +def _ResetRaidOnRestart(): + with RAID_LOCK: + raid = SESSION.query(RaidMode).all() + for r in raid: + r.status = False + SESSION.commit() + + +# it uses a cron job to turn off so if the bot restarts and there is a pending raid disable job then raid will stay on +_ResetRaidOnRestart() diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..1c9254678688bfc57ecf11a2a6659babb950929d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.11.9 + +WORKDIR /root/Mikobot + +COPY . . + +RUN apt-get install -y ffmpeg python3-pip curl +RUN pip3 install --upgrade pip setuptools + +RUN pip install -U -r requirements.txt + +CMD python3 -m Mikobot \ No newline at end of file diff --git a/Extra/Calistoga-Regular.ttf b/Extra/Calistoga-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6ba77fb3765a5dc7513f9b84c6f295eda9900510 Binary files /dev/null and b/Extra/Calistoga-Regular.ttf differ diff --git a/Extra/MutantAcademyStyle.ttf b/Extra/MutantAcademyStyle.ttf new file mode 100644 index 0000000000000000000000000000000000000000..00c56493dfd1c1e074b307d3e977844568ec5ebb Binary files /dev/null and b/Extra/MutantAcademyStyle.ttf differ diff --git a/Extra/a.jpg b/Extra/a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..96710be888dd1f49b6eced02c961bf4a18853c7d Binary files /dev/null and b/Extra/a.jpg differ diff --git a/Extra/bgg.jpg b/Extra/bgg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f437aebeeb93e77da03b56c514c7fb40e65f85a7 Binary files /dev/null and b/Extra/bgg.jpg differ diff --git a/Extra/default.ttf b/Extra/default.ttf new file mode 100644 index 0000000000000000000000000000000000000000..114e6c1968b663086409144e50e7a592bff1d6c2 Binary files /dev/null and b/Extra/default.ttf differ diff --git a/Extra/profilepic.png b/Extra/profilepic.png new file mode 100644 index 0000000000000000000000000000000000000000..5f6674f63cebc5915e2c5fb819e2cdd0d6af97fe Binary files /dev/null and b/Extra/profilepic.png differ diff --git a/Infamous/karma.py b/Infamous/karma.py new file mode 100644 index 0000000000000000000000000000000000000000..eb4392c09e965875444afa21da04459c5b9e0790 --- /dev/null +++ b/Infamous/karma.py @@ -0,0 +1,90 @@ +# https://github.com/Infamous-Hydra/YaeMiko +# https://github.com/Team-ProjectCodeX +# https://t.me/O_okarma + +# <============================================== IMPORTS =========================================================> +from pyrogram.types import InlineKeyboardButton as ib +from telegram import InlineKeyboardButton + +from Mikobot import BOT_USERNAME, OWNER_ID, SUPPORT_CHAT + +# <============================================== CONSTANTS =========================================================> +START_IMG = [ + "https://telegra.ph/file/40b93b46642124605e678.jpg", + "https://telegra.ph/file/01a2e0cd1b9d03808c546.jpg", + "https://telegra.ph/file/ed4385c26dcf6de70543f.jpg", + "https://telegra.ph/file/33a8d97739a2a4f81ddde.jpg", + "https://telegra.ph/file/cce9038f6a9b88eb409b5.jpg", + "https://telegra.ph/file/262c86393730a609cdade.jpg", + "https://telegra.ph/file/33a8d97739a2a4f81ddde.jpg", +] + +HEY_IMG = "https://telegra.ph/file/33a8d97739a2a4f81ddde.jpg" + +ALIVE_ANIMATION = [ + "https://telegra.ph//file/f9e2b9cdd9324fc39970a.mp4", + "https://telegra.ph//file/8d4d7d06efebe2f8becd0.mp4", + "https://telegra.ph//file/c4c2759c5fc04cefd207a.mp4", + "https://telegra.ph//file/b1fa6609b1c4807255927.mp4", + "https://telegra.ph//file/f3c7147da6511fbe27c25.mp4", + "https://telegra.ph//file/39071b73c02e3ff5945ca.mp4", + "https://telegra.ph//file/8d4d7d06efebe2f8becd0.mp4", + "https://telegra.ph//file/6efdd8e28756bc2f6e53e.mp4", +] + +FIRST_PART_TEXT = "✨ *ʜᴇʟʟᴏ* `{}` . . ." + +PM_START_TEXT = "✨ *ɪ ᴀᴍ ᴍɪᴋᴏ, ᴀ ɢᴇɴꜱʜɪɴ ɪᴍᴘᴀᴄᴛ ᴛʜᴇᴍᴇᴅ ʀᴏʙᴏᴛ ᴡʜɪᴄʜ ᴄᴀɴ ʜᴇʟᴘ ʏᴏᴜ ᴛᴏ ᴍᴀɴᴀɢᴇ ᴀɴᴅ ꜱᴇᴄᴜʀᴇ ʏᴏᴜʀ ɢʀᴏᴜᴘ ᴡɪᴛʜ ʜᴜɢᴇ ɢʀᴏᴜᴘ ᴍᴀɴᴀɢᴇᴍᴇɴᴛ*" + +START_BTN = [ + [ + InlineKeyboardButton( + text="⇦ ADD ME ⇨", + url=f"https://t.me/{BOT_USERNAME}?startgroup=true", + ), + ], + [ + InlineKeyboardButton(text="HELP", callback_data="extra_command_handler"), + ], + [ + InlineKeyboardButton(text="DETAILS", callback_data="Miko_"), + InlineKeyboardButton(text="SOURCE", callback_data="git_source"), + ], + [ + InlineKeyboardButton(text="CREATOR", url=f"tg://user?id={OWNER_ID}"), + ], +] + +GROUP_START_BTN = [ + [ + InlineKeyboardButton( + text="⇦ ADD ME ⇨", + url=f"https://t.me/{BOT_USERNAME}?startgroup=true", + ), + ], + [ + InlineKeyboardButton(text="SUPPORT", url=f"https://t.me/{SUPPORT_CHAT}"), + InlineKeyboardButton(text="CREATOR", url=f"tg://user?id={OWNER_ID}"), + ], +] + +ALIVE_BTN = [ + [ + ib(text="UPDATES", url="https://t.me/Hydra_Updates"), + ib(text="SUPPORT", url="https://t.me/hydraXsupport"), + ], + [ + ib( + text="⇦ ADD ME ⇨", + url=f"https://t.me/{BOT_USERNAME}?startgroup=true", + ), + ], +] + +HELP_STRINGS = """ +🫧 *Yae-Miko* 🫧 [ㅤ](https://telegra.ph/file/b05535884267a19ee5c93.jpg) + +☉ *Here, you will find a list of all the available commands.* + +ᴀʟʟ ᴄᴏᴍᴍᴀɴᴅs ᴄᴀɴ ʙᴇ ᴜsᴇᴅ ᴡɪᴛʜ : / +""" diff --git a/Infamous/temp.py b/Infamous/temp.py new file mode 100644 index 0000000000000000000000000000000000000000..7e69769bbdf2a7016bd5b115d0d32d6155a05ef2 --- /dev/null +++ b/Infamous/temp.py @@ -0,0 +1,122 @@ +# <============================================== IMPORTS =========================================================> +import asyncio +import os +from logging import getLogger +from typing import Union + +from pymongo import MongoClient +from pyrogram.errors import ( + FloodWait, + InputUserDeactivated, + PeerIdInvalid, + UserIsBlocked, +) +from pyrogram.types import Message + +from Mikobot import DB_NAME, MONGO_DB_URI + +client = MongoClient(MONGO_DB_URI) +dbname = client[DB_NAME] + +LOGGER = getLogger(__name__) +BANNED = {} +# <=======================================================================================================> + + +# <=================================================== CLASS ====================================================> +# temp db for banned +class temp(object): + BANNED_USERS = [] + BANNED_CHATS = [] + ME = None + CURRENT = int(os.environ.get("SKIP", 2)) + CANCEL = False + MELCOW = {} + U_NAME = None + B_NAME = None + + +# <=======================================================================================================> + + +# <================================================ FUNCTION =======================================================> +def broadcast_messages(user_id, message): + try: + message.copy(chat_id=user_id) + return True, "Succes" + except FloodWait as e: + asyncio.sleep(e.x) + return broadcast_messages(user_id, message) + except InputUserDeactivated: + dbname.delete_user(int(user_id)) + LOGGER.info(f"{user_id}-Removed from Database, since deleted account.") + return False, "Deleted" + except UserIsBlocked: + LOGGER.info(f"{user_id} -Blocked the bot.") + return False, "Blocked" + except PeerIdInvalid: + dbname.delete_user(int(user_id)) + LOGGER.info(f"{user_id} - PeerIdInvalid") + return False, "Error" + except Exception: + return False, "Error" + + +def get_size(size): + """Get size in readable format""" + + units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB"] + size = float(size) + i = 0 + while size >= 1024.0 and i < len(units): + i += 1 + size /= 1024.0 + return "%.2f %s" % (size, units[i]) + + +def get_file_id(msg: Message): + if msg.media: + for message_type in ( + "photo", + "animation", + "audio", + "document", + "video", + "video_note", + "voice", + "sticker", + ): + if obj := getattr(msg, message_type): + setattr(obj, "message_type", message_type) + return obj + + +def extract_user(message: Message) -> Union[int, str]: + """extracts the user from a message""" + # https://github.com/SpEcHiDe/PyroGramBot/blob/f30e2cca12002121bad1982f68cd0ff9814ce027/pyrobot/helper_functions/extract_user.py#L7 + user_id = None + user_first_name = None + if message.reply_to_message: + user_id = message.reply_to_message.from_user.id + user_first_name = message.reply_to_message.from_user.first_name + + elif len(message.command) > 1: + if len(message.entities) > 1 and message.entities[1].type == "text_mention": + required_entity = message.entities[1] + user_id = required_entity.user.id + user_first_name = required_entity.user.first_name + else: + user_id = message.command[1] + # don't want to make a request -_- + user_first_name = user_id + try: + user_id = int(user_id) + except ValueError: + pass + else: + user_id = message.from_user.id + user_first_name = message.from_user.first_name + return (user_id, user_first_name) + + +# <================================================ END =======================================================> diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4cc7927b5226c2dd49e18e5c3c6931991e01d107 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Infamous-Hydra & ProjectCodeX + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Procfile b/Procfile new file mode 100644 index 0000000000000000000000000000000000000000..370e7d708070bf57a751033d5c54353ec1db27bb --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +worker: python3 -m Mikobot diff --git a/README.md b/README.md index 2c858287c6f5d103d2c86cd4b21fc55f6ca8d1e4..979982e56665f6ac136a49fff191a26ca2c073f0 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ python3 -m Mikobot # 8. Run the Bot

Click the button below to deploy YAE ダ MIKO on Heroku and enjoy its enhanced features and user-friendly interface!

- + Deploy

@@ -78,12 +78,11 @@ python3 -m Mikobot # 8. Run the Bot

## CREDITS -+ [Karma](https://github.com/Infamous-Hydra) -+ [PaulSonOfLars](https://github.com/PaulSonOfLars) +The foundation of the application is rooted in the initial efforts undertaken by [PaulSonOfLars](https://github.com/PaulSonOfLars) + [lostb053](https://github.com/lostb053) > Anime + [TheHamkerCat](https://github.com/TheHamkerCat) > Python-Arq + [ProjectCodeX](https://github.com/Team-ProjectCodeX) > Modules Others in the commit history and files. If anything is missing, feel free to [![gmail](https://img.shields.io/badge/mail-Here-red?style=for-the-badge&logo=gmail)](mailto:makandu2054@gmail.com) or submit a pull request. -> Try this bot [@YaeMiko_Roxbot](https://t.me/YaeMiko_Roxbot); it's made with this repository for demonstration purposes. +> Try this bot [@MIKO_V2BOT](https://t.me/MIKO_V2BOT); it's made with this repository for demonstration purposes. diff --git a/app.json b/app.json new file mode 100644 index 0000000000000000000000000000000000000000..31b9d2f9b6a00ce044976c0b63b9395020332330 --- /dev/null +++ b/app.json @@ -0,0 +1,117 @@ +{ + "name": "yAE-mIKO", + "description": "A versatile Telegram Group Management Bot.", + "logo": "https://telegra.ph/file/4d370b478d90d98709398.jpg", + "keywords": ["telegram", "modular", "group", "management", "Miko"], + "repository": "https://github.com/Infamous-Hydra/YaeMiko", + "stack": "heroku-24", + "formation": { + "worker": { + "quantity": 1, + "size": "eco" + } + }, + "addons": [ + { + "plan": "heroku-postgresql", + "options": { + "version": "16" + } + } + ], + "env": { + "API_ID": { + "description": "Obtain this value from my.telegram.org/apps.", + "required": true, + "value": "204" + }, + "API_HASH": { + "description": "Obtain this value from my.telegram.org/apps.", + "required": true, + "value": "2df" + }, + "DEL_CMDS": { + "description": "Set this to True to delete command messages from users without the necessary permissions.", + "value": "True" + }, + "ENV": { + "description": "Setting this to ANYTHING will enable environment variables. Leave it as it is.", + "required": true, + "value": "True" + }, + "EVENT_LOGS": { + "description": "Channel for event logs to note down important bot-level events. Recommend making this public. Example: '-123456'", + "required": true, + "value": "-100" + }, + "MESSAGE_DUMP": { + "description": "Support chat ID.", + "required": true, + "value": "-100" + }, + "MONGO_DB_URI": { + "description": "Required for MongoDB database connections.", + "required": true, + "value": "mongodb+srv://" + }, + "DATABASE_URL": { + "description": "Required for SQL database connections.", + "required": true, + "value": "postgres://" + }, + "OWNER_ID": { + "description": "Your user ID as an integer.", + "required": true, + "value": "5907205317" + }, + "DEV_USERS": { + "description": "A space separated list of user IDs who you want to assign as sudo users.", + "required": false, + "value": "5907205317" + }, + "DRAGONS": { + "description": "A space separated list of user IDs who you want to assign as sudo users.", + "required": false, + "value": "" + }, + "STRICT_GBAN": { + "description": "Enforce gbans across new groups as well as old groups. When a gbanned user talks, he will be banned.", + "value": "True" + }, + "DEMONS": { + "description": "A space separated list of user IDs who can use Gban From Your Bot", + "required": false, + "value": "5555455171 5739199900" + }, + "TIGERS": { + "description": "A space separated list of user IDs who can not be banned by your Bot", + "required": false, + "value": "5907205317" + }, + "WOLVES": { + "description": "A space separated list of user IDs who you want to assign as whitelisted - can't be banned with your bot.", + "required": false, + "value": "5907205317" + }, + "SUPPORT_CHAT": { + "description": "Your Telegram support group chat username. Users will reach out for assistance. Example: MyGroupChatUsernameBlah. Avoid pointing to Miko Support to prevent conflicts.", + "required": true, + "value": "" + }, + "SUPPORT_ID": { + "description": "Support chat ID.", + "required": true, + "value": "-100" + }, + "DB_NAME": { + "description": "Your MongoDB name.", + "required": true, + "value": "MikoDB" + }, + "TOKEN": { + "description": "Bot token obtained from @BotFather on Telegram.", + "required": true, + "value": "" + } + } +} diff --git a/heroku.yml b/heroku.yml new file mode 100644 index 0000000000000000000000000000000000000000..33f0d00f1d5395be751b009e007a77e9c84f445e --- /dev/null +++ b/heroku.yml @@ -0,0 +1,5 @@ +build: + docker: + worker: Dockerfile +run: + worker: python3 -m Mikobot diff --git a/locales/__init__.py b/locales/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/locales/en-US/afk.json b/locales/en-US/afk.json new file mode 100644 index 0000000000000000000000000000000000000000..bdd7f0f73abdc00b1e47891655803e49d9174b5a --- /dev/null +++ b/locales/en-US/afk.json @@ -0,0 +1,13 @@ +{ + "no_channel": "This feature not supported for channel.", + "on_afk_msg_no_r": "**{usr}** [{id}] is back online and was away for {tm}\n\n", + "on_afk_msg_with_r": "**{usr}** [{id}] is back online and was away for {tm}\n\n**Reason:** `{reas}`\n\n", + "is_afk_msg_no_r": "**{usr}** [{id}] is AFK since {tm} ago.\n\n", + "is_afk_msg_with_r": "**{usr}** [{id}] is AFK since {tm} ago.\n\n**Reason:** {reas}\n\n", + "is_online": "**{usr}** [{id}] is back online", + "now_afk": "{usr} [{id}] is now AFK!.", + "afkdel_help": "**Usage:**\n/{cmd} [ENABLE|DISABLE] to enable or disable auto delete message.", + "afkdel_disable": "Disabled auto delete AFK message.", + "afkdel_enable": "Enabled auto delete AFK message in this chat.", + "is_afk": "{usr} [{id}] is AFK!." +} \ No newline at end of file diff --git a/locales/en-US/sangmata.json b/locales/en-US/sangmata.json new file mode 100644 index 0000000000000000000000000000000000000000..1b17089d3bade1b98282c8e7e779543675097c84 --- /dev/null +++ b/locales/en-US/sangmata.json @@ -0,0 +1,13 @@ +{ + "no_uname": "No Username", + "no_last_name": "No Last Name", + "uname_change_msg": "✨ Changed username from {bef} ➡️ {aft}.\n", + "lastname_change_msg": "✨ Changed last name from {bef} ➡️ {aft}.\n", + "firstname_change_msg": "✨ Changed first name from {bef} ➡️ {aft}.\n", + "set_sangmata_help": "Use /{cmd} on, to enable Mikomata. If you want disable, you can use off parameter.", + "sangmata_already_on": "Mikomata already enabled in your groups.", + "sangmata_enabled": "Mikomata enabled in your groups.", + "sangmata_already_off": "Mikomata already disabled in your groups.", + "sangmata_disabled": "Mikomata disabled in your groups.", + "wrong_param": "Unknown parameter, use only on/off parameter." +} diff --git a/locales/en-US/stickers.json b/locales/en-US/stickers.json new file mode 100644 index 0000000000000000000000000000000000000000..c2c55e88f6f016dbe6217e0a7532e4ff75e99749 --- /dev/null +++ b/locales/en-US/stickers.json @@ -0,0 +1,19 @@ +{ + "no_anim_stick": "Animated sticker is not supported!", + "not_sticker": "This is not a sticker!", + "unkang_msg": "Trying to remove from pack..", + "unkang_success": "Sticker has been removed from your pack", + "unkang_error": "Failed remove sticker from your pack.\n\nERR: {e}", + "unkang_help": "Please reply sticker that created by {c} to remove sticker from your pack.", + "anon_warn": "You are anon admin, kang stickers in my pm.", + "kang_msg": "Trying to steal your sticker...", + "stick_no_name": "The sticker has no name.", + "kang_help": "Want me to guess the sticker? Please tag a sticker.", + "exist_pack": "Using existing sticker pack...", + "new_packs": "Creating a new sticker pack...", + "please_start_msg": "It looks like you've never interacted with me in private chat, you need to do that first..", + "click_me": "Click Me", + "pack_full": "Your Sticker Pack is full if your pack is not in v1 Type /kang 1, if it is not in v2 Type /kang 2 and so on.", + "viewpack": "VIEW PACK", + "kang_success": "Your sticker has been added! \nFor fast update remove your pack & add again.\nEmoji: {emot}" +} \ No newline at end of file diff --git a/locales/id-ID/afk.json b/locales/id-ID/afk.json new file mode 100644 index 0000000000000000000000000000000000000000..545faac3b1b5cc7dc41e55e3ab940cf85837b080 --- /dev/null +++ b/locales/id-ID/afk.json @@ -0,0 +1,13 @@ +{ + "no_channel": "Fitur ini tidak didukung untuk channel.", + "on_afk_msg_no_r": "**{usr}** [{id}] kembali online dan telah AFK selama {tm}\n\n", + "on_afk_msg_with_r": "**{usr}** [{id}] kembali online dan telah AFK selama {tm}\n\n**Alasan:** `{reas}`\n\n", + "is_afk_msg_no_r": "**{usr}** [{id}] telah AFK sejak {tm} yang lalu.\n\n", + "is_afk_msg_with_r": "**{usr}** [{id}] telah AFK sejak {tm} yang lalu.\n\n**Alasan:** {reas}\n\n" , + "is_online": "**{usr}** [{id}] kembali online", + "now_afk": "{usr} [{id}] sekarang AFK!.", + "afkdel_help": "**Penggunaan:**\n/{cmd} [ENABLE|DISABLE] untuk mengaktifkan atau menonaktifkan hapus pesan AFK secara otomatis.", + "afkdel_disable": "Penghapusan otomatis pesan AFK dinonaktifkan.", + "afkdel_enable": "Penghapusan otomatis pesan AFK di obrolan ini diaktifkan.", + "is_afk": "{usr} [{id}] sedang AFK!." +} diff --git a/locales/id-ID/sangmata.json b/locales/id-ID/sangmata.json new file mode 100644 index 0000000000000000000000000000000000000000..52417d107691cf87049316fefadfcac0b2d5b2fa --- /dev/null +++ b/locales/id-ID/sangmata.json @@ -0,0 +1,13 @@ +{ + "no_uname": "Tanpa Username", + "no_last_name": "Tanpa Nama Belakang", + "uname_change_msg": "✨ Mengubah username dari {bef} ➡️ {aft}.\n", + "lastname_change_msg": "✨ Mengubah nama belakang dari {bef} ➡️ {aft}.\n", + "firstname_change_msg": "✨ Mengubah nama depan dari {bef} ➡️ {aft}.\n", + "set_sangmata_help": "Gunakan /{cmd} on, untuk mengaktifkan sangmata. Jika Anda ingin menonaktifkan, Anda dapat menggunakan parameter off.", + "sangmata_already_on": "SangMata telah diaktifkan di grup Anda.", + "sangmata_enabled": "Sangmata diaktifkan di grup Anda.", + "sangmata_already_off": "SangMata telah dinonaktifkan di grup Anda.", + "sangmata_disabled": "Sangmata dinonaktifkan di grup Anda.", + "wrong_param": "Parameter tidak diketahui, gunakan hanya parameter hidup/mati." +} \ No newline at end of file diff --git a/locales/id-ID/stickers.json b/locales/id-ID/stickers.json new file mode 100644 index 0000000000000000000000000000000000000000..cd69b7117d92c359ec4f7246e5e4169d498e9177 --- /dev/null +++ b/locales/id-ID/stickers.json @@ -0,0 +1,19 @@ +{ + "no_anim_stick": "Stiker animasi tidak didukung!", + "not_sticker": "Ini bukan stiker!", + "unkang_msg": "Mencoba menghapus dari paket..", + "unkang_success": "Stiker telah dihapus dari paket Anda", + "unkang_error": "Gagal menghapus stiker dari paket Anda.\n\nERR: {e}", + "unkang_help": "Tolong balas stiker yang dibuat oleh {c} untuk menghapus stiker dari paket Anda.", + "anon_warn": "Anda adalah admin anon, stiker kang ada di pm saya.", + "kang_msg": "Mencoba mencuri stiker Anda...", + "stick_no_name": "Stiker tidak memiliki nama.", + "kang_help": "Ingin saya menebak stikernya? Harap tandai stiker.", + "exist_pack": "Menggunakan paket stiker yang ada...", + "new_packs": "Membuat paket stiker baru...", + "please_start_msg": "Tampaknya Anda belum pernah berinteraksi dengan saya dalam obrolan pribadi, Anda harus melakukannya dulu..", + "click_me": "Klik Saya", + "pack_full": "Paket Stiker Anda penuh jika paket Anda tidak dalam Tipe v1 /kang 1, jika tidak dalam Tipe v2 /kang 2 dan seterusnya.", + "viewpack": "👀 Lihat Paket", + "kang_success": "Stiker berhasil dicuri!\nEmoji: {emot}" +} \ No newline at end of file diff --git a/locales/id-JW/afk.json b/locales/id-JW/afk.json new file mode 100644 index 0000000000000000000000000000000000000000..732ad35d720e92711d643ba33026556e2c4d3972 --- /dev/null +++ b/locales/id-JW/afk.json @@ -0,0 +1,13 @@ +{ + "no_channel": "Fitur iki ora didhukung kanggo channel.", + "on_afk_msg_no_r": "**{usr}** [{id}] wis online maneh lan ora ana suwene {tm}\n\n", + "on_afk_msg_with_r": "**{usr}** [{id}] wis online maneh lan ora ana suwene {tm}\n\n**Alesan:** `{reas}`\n\n", + "is_afk_msg_no_r": "**{usr}** [{id}] iku AFK wiwit {tm} kepungkur.\n\n", + "is_afk_msg_with_r": "**{usr}** [{id}] iku AFK wiwit {tm} kepungkur.\n\n**Alesan:** {reas}\n\n" , + "is_online": "**{usr}** [{id}] wis online maneh", + "now_afk": "{usr} [{id}] saiki dadi AFK!.", + "afkdel_help": "**Panganggone:**\n/{cmd} [ENABLE|DISABLE] kanggo ngaktifake utawa mateni pesen otomatis mbusak.", + "afkdel_disable": "Mbusak pesen AFK otomatis dipateni.", + "afkdel_enable": "Mbusak pesen AFK otomatis ing obrolan iki diaktifake.", + "is_afk": "{usr} [{id}] iku AFK!." +} diff --git a/locales/id-JW/sangmata.json b/locales/id-JW/sangmata.json new file mode 100644 index 0000000000000000000000000000000000000000..c40ea3dd6d54d901f3c89142a8177a5ecbf2f0b4 --- /dev/null +++ b/locales/id-JW/sangmata.json @@ -0,0 +1,13 @@ +{ + "no_uname": "Ora Username", + "no_last_name": "Ora Ana Jeneng mburi", + "uname_change_msg": "✨ Username diganti saka {bef} ➡️ {aft}.\n", + "lastname_change_msg": "✨ Ganti jeneng mburi saka {bef} ➡️ {aft}.\n", + "firstname_change_msg": "✨ Ganti jeneng ngarep saka {bef} ➡️ {aft}.\n", + "set_sangmata_help": "Gunakake /{cmd} on, kanggo ngaktifake sangmata. Yen sampeyan pengin mateni, sampeyan bisa nggunakake parameter mati.", + "sangmata_already_on": "SangMata wis diaktifake ing grup sampeyan.", + "sangmata_enabled": "Sangmata diaktifake ing grup sampeyan.", + "sangmata_already_off": "SangMata wis dipateni ing grup sampeyan.", + "sangmata_disabled": "Sangmata dipateni ing grup sampeyan.", + "wrong_param": "Parameter sing ora dingerteni, gunakake mung parameter aktif/mati." +} \ No newline at end of file diff --git a/locales/id-JW/stickers.json b/locales/id-JW/stickers.json new file mode 100644 index 0000000000000000000000000000000000000000..19e38a7ea037a2c368d73c652e76c47e0402b0d4 --- /dev/null +++ b/locales/id-JW/stickers.json @@ -0,0 +1,19 @@ +{ + "no_anim_stick": "Stiker animasi ora didhukung!", + "not_sticker": "Iki dudu stiker!", + "unkang_msg": "Nyoba nyopot saka pack..", + "unkang_success": "Stiker wis dibusak saka pack panjenengan", + "unkang_error": "Gagal mbusak stiker saka paket sampeyan.\n\nERR: {e}", + "unkang_help": "Mangga wangsulana stiker sing digawe {c} kanggo mbusak stiker saka paket sampeyan.", + "anon_warn": "Sampeyan anon admin, kang stiker ing pmku.", + "kang_msg": "Nyolong stiker sampeyan...", + "stick_no_name": "Stiker ora ana jeneng.", + "kang_help": "Arep tak tebak stikere? Mangga tag stiker.", + "exist_pack": "Nganggo paket stiker sing ana...", + "new_packs": "Nggawe pak stiker anyar...", + "please_start_msg": "Koyone sampeyan ora tau sesambungan karo aku ing obrolan pribadi, sampeyan kudu nglakoni dhisik..", + "click_me": "Klik Aku", + "pack_full": "Paket Stiker sampeyan kebak yen paket sampeyan ora ana ing tipe v1 /kang 1, yen ora ana ing Tipe v2 /kang 2 lan sapiturute.", + "viewpack": "👀 Deleng Paket", + "kang_success": "Stiker kasil dicolong!\nEmoji: {emot}" +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..0508494cae86cc54fd1c6cd5df2091b0049df90a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,81 @@ +# ==== A ==== +aiofiles==24.1.0 +aiohttp==3.10.4 +APScheduler==3.10.4 +alphabet-detector==0.0.7 +asyncio==3.4.3 + +# ==== B ==== +beautifulsoup4==4.12.3 +bleach==6.1.0 + +# ==== C ==== +cachetools==5.5.0 + +cockroachdb + +# ==== D ==== +dateparser==1.2.0 + +# ==== E ==== +emoji==2.12.1 +emoji-country-flag==2.0.1 + +# ==== F ==== +ffmpeg-python +future==1.0.0 + +# ==== G ==== +gpytranslate==1.5.1 + +# ==== H ==== +html2text==2024.2.26 +httpx[http2] +humanize==4.10.0 + +# ==== J ==== +jikanpy==4.3.2 + +# ==== L ==== +lxml==5.3.0 + +# ==== M ==== +markdown2==2.5.0 +motor==3.5.1 + +# ==== N ==== +nekos.py==1.1.0 +numpy>=1.23.5 + +# ==== O ==== +opencv-python-headless==4.10.0.84 + +# ==== P ==== +Pillow==9.5.0 +psutil==6.0.0 +pyjokes +psycopg2-binary==2.9.9 +pynewtonmath==1.0.1 +pymongo[srv]==4.8.0 +pyrate-limiter==3.7.0 +pytz==2024.1 +python-arq==6.0.7 +pyrogram==2.0.106 +python-telegram-bot==21.4 + +# ==== R ==== +regex==2024.7.24 +requests==2.32.3 + +# ==== S ==== +shortuuid==1.0.13 +SQLAlchemy==1.4.52 +speedtest-cli==2.1.3 + +# ==== T ==== +telegraph==2.2.0 +telethon==1.36.0 +tgcrypto==1.2.5 + +# ==== U ==== +Unidecode==1.3.8 diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 0000000000000000000000000000000000000000..546f3c8de171cfec01188be38bc1466f33545866 --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-3.11.9 diff --git a/variables.py b/variables.py new file mode 100644 index 0000000000000000000000000000000000000000..4f0112d2c3239e536c247c5eb38fbf9f732c26c8 --- /dev/null +++ b/variables.py @@ -0,0 +1,97 @@ +# https://github.com/Infamous-Hydra/YaeMiko +# https://github.com/Team-ProjectCodeX + + +import json +import os + + +def get_user_list(config, key): + with open("{}/Mikobot/{}".format(os.getcwd(), config), "r") as json_file: + return json.load(json_file)[key] + + +class Config(object): + # Configuration class for the bot + + # Enable or disable logging + LOGGER = True + + # <================================================ REQUIRED ======================================================> + # Telegram API configuration + API_ID = 204 # Get this value from my.telegram.org/apps + API_HASH = "" + + # Database configuration (PostgreSQL) + DATABASE_URL = "postgres:" + + # Event logs chat ID and message dump chat ID + EVENT_LOGS = -100 + MESSAGE_DUMP = -100 + + # MongoDB configuration + MONGO_DB_URI = "" + + # Support chat and support ID + SUPPORT_CHAT = "" + SUPPORT_ID = -100 + + # Database name + DB_NAME = "" + + # Bot token + TOKEN = "" # Get bot token from @BotFather on Telegram + + # Owner's Telegram user ID (Must be an integer) + OWNER_ID = 5907205317 + # <=======================================================================================================> + + # <================================================ OPTIONAL ======================================================> + # Optional configuration fields + + # List of groups to blacklist + BL_CHATS = [] + + # User IDs of sudo users, dev users, support users, tiger users, and whitelist users + DRAGONS = get_user_list("elevated_users.json", "sudos") + DEV_USERS = get_user_list("elevated_users.json", "devs") + DEMONS = get_user_list("elevated_users.json", "supports") + TIGERS = get_user_list("elevated_users.json", "tigers") + WOLVES = get_user_list("elevated_users.json", "whitelists") + + # Toggle features + ALLOW_CHATS = True + ALLOW_EXCL = True + DEL_CMDS = True + INFOPIC = True + + # Modules to load or exclude + LOAD = [] + NO_LOAD = [] + + # Global ban settings + STRICT_GBAN = True + BAN_STICKER = ( + "CAACAgUAAxkBAAEGWC5lloYv1tiI3-KPguoH5YX-RveWugACoQ4AAi4b2FQGdUhawbi91DQE" + ) + + # Temporary download directory + TEMP_DOWNLOAD_DIRECTORY = "./" + # <=======================================================================================================> + + +# <=======================================================================================================> + + +class Production(Config): + # Production configuration (inherits from Config) + + # Enable or disable logging + LOGGER = True + + +class Development(Config): + # Development configuration (inherits from Config) + + # Enable or disable logging + LOGGER = True