Sfoglia il codice sorgente

Merge pull request #1 from thehamkercat/pnut-dev

Pnut-dev
Akshay Rajput 5 anni fa
parent
commit
e108cdbfba

+ 133 - 5
.gitignore

@@ -1,5 +1,133 @@
-config.ini
-WilliamButcher.session
-wbb/WilliamButcher.session
-wbb/config.ini
-wbb/__pycache__
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+*.ini
+*.pyc
+*.session
+*.session-journal
+
+
+#others
+wbb/logs/error.log

+ 12 - 2
README.md

@@ -13,7 +13,7 @@ Just Another Telegram Bot Written In Python Using Pyrogram.
 
 1. `git clone https://github.com/thehamkercat/WilliamButcherBot`, to download the source code.
 2. `cd wbb`, to enter the directory.
-3. `pip install -r requirements.txt`, to install the requirements.
+3. `pip3 install -r requirements.txt`, to install the requirements.
 4. `cp sample_config.ini config.ini` It should look like the code below:<br>
 
 ```
@@ -21,9 +21,19 @@ Just Another Telegram Bot Written In Python Using Pyrogram.
    api_id = 12345
    api_hash = 0123456789abcdef0123456789abcdef
    bot_token = 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
+   
+   [admin]
+   owner_id = 1243703097
+
+   [prefix]
+   prefixes = /
+
+   [mods]
+   load_modules =
+   noload_modules =
    ```
 5. Run with `python3 -m wbb`
 
 ## License
 
-MIT © 2019-2020 [Dan](//github.com/delivrance)
+MIT © 2019-2020

+ 4 - 1
requirements.txt

@@ -1,2 +1,5 @@
 psutil
-shutil
+uvloop
+aiohttp
+pyrogram==1.0.7
+TgCrypto

+ 14 - 0
sample_config.ini

@@ -0,0 +1,14 @@
+[pyrogram]
+bot_token =
+api_id =
+api_hash =
+
+[admin]
+owner_id =
+
+[prefix]
+prefixes = / !
+
+[mods]
+load_modules = 
+noload_modules = 

+ 41 - 0
wbb/__init__.py

@@ -0,0 +1,41 @@
+from pyrogram import Client
+from configparser import ConfigParser
+import logging
+import time
+import os
+
+
+if os.path.exists("wbb/logs/error.log"):
+    f = open("wbb/logs/error.log", "w")
+    f.write("PEAK OF LOG FILE")
+
+LOG_FORMAT = (
+    "[%(asctime)s.%(msecs)03d] %(filename)s:%(lineno)s %(levelname)s: %(message)s"
+)
+
+logging.basicConfig(
+    level=logging.INFO,
+    format=LOG_FORMAT,
+    datefmt="%m-%d %H:%M",
+    filename="wbb/logs/error.log",
+    filemode="w",
+)
+
+console = logging.StreamHandler()
+console.setLevel(logging.ERROR)
+formatter = logging.Formatter(LOG_FORMAT)
+console.setFormatter(formatter)
+logging.getLogger("").addHandler(console)
+
+log = logging.getLogger()
+
+config = ConfigParser()
+config.read("config.ini")
+OWNER_ID = int(config.get("admin", "owner_id"))
+Command = config.get("prefix", "prefixes").split()
+MOD_LOAD = config.get("mods", "load_modules").split()
+MOD_NOLOAD = config.get("mods", "noload_modules").split()
+bot_start_time = time.time()
+
+
+app = Client(":memory:")

+ 171 - 2
wbb/__main__.py

@@ -1,4 +1,173 @@
-from .wbb import app
+import asyncio
+import re
+from pyrogram import filters, idle
+from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup
+from wbb import app, MOD_NOLOAD, MOD_LOAD, Command
+from wbb.utils import random_line, get_info, paginate_modules, cust_filter
+import importlib
+from wbb.modules import ALL_MODULES
+import uvloop
+
+
+loop = asyncio.get_event_loop()
+
+
+HELPABLE = {}
+
+
+async def start_bot():
+    await app.start()
+    await get_info(app)
+    from wbb.utils import botinfo
+
+    await main_bot()
+    for modul in ALL_MODULES:
+        imported_module = importlib.import_module("wbb.modules." + modul)
+        if hasattr(imported_module, "__MODULE__") and imported_module.__MODULE__:
+            if imported_module.__MODULE__.lower() in MOD_NOLOAD:
+                continue
+            imported_module.__MODULE__ = imported_module.__MODULE__
+            if hasattr(imported_module, "__HELP__") and imported_module.__HELP__:
+                HELPABLE[imported_module.__MODULE__.lower()] = imported_module
+    bot_modules = ""
+    j = 1
+    for i in ALL_MODULES:
+        if j == 4:
+            bot_modules += "|{:<15}|\n".format(i)
+            j = 0
+        else:
+            bot_modules += "|{:<15}".format(i)
+        j += 1
+    print("+===============================================================+")
+    print("|                         WBB - Modules                         |")
+    print("+===============+===============+===============+===============+")
+    print(bot_modules)
+    print("+===============+===============+===============+===============+")
+    print("Bot run successfully as {}!".format(botinfo.BOT_NAME))
+    await idle()
+
+
+async def main_bot():
+    from wbb.utils import botinfo
+
+    @app.on_message(cust_filter.command("start"))
+    async def start(client, message):
+        if message.chat.type != "private":
+            await message.reply("Message me Privately *winks*")
+            return
+        await message.reply("Hi, try /help")
+
+    @app.on_message(cust_filter.command("help"))
+    async def help_command(_, message):
+        if message.chat.type != "private":
+            if len(message.command) >= 2 and message.command[1] == "help":
+                text, keyboard = await help_parser(message)
+                await message.reply(
+                    text, reply_markup=keyboard, disable_web_page_preview=True
+                )
+                return
+            keyboard = InlineKeyboardMarkup(
+                [
+                    [
+                        InlineKeyboardButton(
+                            text="Help",
+                            url="t.me/" + botinfo.BOT_USERNAME + "?start=help",
+                        )
+                    ]
+                ]
+            )
+            await message.reply("Contact me at PM.", reply_markup=keyboard)
+            return
+        text, keyboard = await help_parser(message)
+        await message.reply(text, reply_markup=keyboard, disable_web_page_preview=True)
+
+    async def help_parser(message, keyboard=None):
+        if not keyboard:
+            keyboard = InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help"))
+        return (
+            "Hi {first_name}. I am {bot_name}, you can use commands with following prefixes{commands}".format(
+                first_name=message.from_user.first_name,
+                bot_name=botinfo.BOT_NAME,
+                commands=", ".join(Command),
+            ),
+            keyboard,
+        )
+
+    @app.on_callback_query(filters.regex(r"help_(.*?)"))
+    async def help_button(c, q):
+        mod_match = re.match(r"help_module\((.+?)\)", q.data)
+        prev_match = re.match(r"help_prev\((.+?)\)", q.data)
+        next_match = re.match(r"help_next\((.+?)\)", q.data)
+        back_match = re.match(r"help_back", q.data)
+        create_match = re.match(r"help_create", q.data)
+
+        if mod_match:
+            module = mod_match.group(1)
+            text = (
+                "{} **{}**:\n".format(
+                    "Here is the help for", HELPABLE[module].__MODULE__
+                )
+                + HELPABLE[module].__HELP__
+            )
+
+            await q.message.edit(
+                text=text,
+                reply_markup=InlineKeyboardMarkup(
+                    [[InlineKeyboardButton("back", callback_data="help_back")]]
+                ),
+                disable_web_page_preview=True,
+            )
+
+        elif prev_match:
+            curr_page = int(prev_match.group(1))
+            await q.message.edit(
+                text="Hi {first_name}. I am {bot_name}, you can use commands with following prefixes{commands}".format(
+                    first_name=q.from_user.first_name,
+                    bot_name=botinfo.BOT_NAME,
+                    commands=", ".join(Command),
+                ),
+                reply_markup=InlineKeyboardMarkup(
+                    paginate_modules(curr_page - 1, HELPABLE, "help")
+                ),
+                disable_web_page_preview=True,
+            )
+
+        elif next_match:
+            next_page = int(next_match.group(1))
+            await q.message.edit(
+                text="Hi {first_name}. I am {bot_name}, you can use commands with following prefixes{commands}".format(
+                    first_name=q.from_user.first_name,
+                    bot_name=botinfo.BOT_NAME,
+                    commands=", ".join(Command),
+                ),
+                reply_markup=InlineKeyboardMarkup(
+                    paginate_modules(next_page + 1, HELPABLE, "help")
+                ),
+                disable_web_page_preview=True,
+            )
+
+        elif back_match:
+            await q.message.edit(
+                text="Hi {first_name}. I am {bot_name}, you can use commands with following prefixes{commands}".format(
+                    first_name=q.from_user.first_name,
+                    bot_name=botinfo.BOT_NAME,
+                    commands=", ".join(Command),
+                ),
+                reply_markup=InlineKeyboardMarkup(
+                    paginate_modules(0, HELPABLE, "help")
+                ),
+                disable_web_page_preview=True,
+            )
+
+        elif create_match:
+            text, keyboard = await help_parser(q)
+            await q.message.edit(
+                text=text, reply_markup=keyboard, disable_web_page_preview=True
+            )
+
+        return await c.answer_callback_query(q.id)
+
 
 if __name__ == "__main__":
-    app.run()
+    uvloop.install()
+    loop.run_until_complete(start_bot())

+ 45 - 0
wbb/modules/__init__.py

@@ -0,0 +1,45 @@
+import importlib
+from wbb import MOD_LOAD, MOD_NOLOAD, log
+
+
+def __list_all_modules():
+    from os.path import dirname, basename, isfile
+    import glob
+
+    # This generates a list of modules in this folder for the * in __main__ to work.
+    mod_paths = glob.glob(dirname(__file__) + "/*.py")
+    all_modules = [
+        basename(f)[:-3]
+        for f in mod_paths
+        if isfile(f)
+        and f.endswith(".py")
+        and not f.endswith("__init__.py")
+        and not f.endswith("__main__.py")
+    ]
+
+    if MOD_LOAD or MOD_NOLOAD:
+        to_Load = MOD_LOAD
+        if to_Load:
+            if not all(
+                any(mod == module_name for module_name in all_modules)
+                for mod in to_Load
+            ):
+                log.error("Invalid Module name!")
+                quit(1)
+
+        else:
+            to_Load = all_modules
+
+        if MOD_NOLOAD:
+            log.info("No load: {}".format(MOD_NOLOAD))
+            return [item for item in to_Load if item not in MOD_NOLOAD]
+
+        return to_Load
+
+    return all_modules
+
+
+importlib.import_module("wbb.modules.__main__")
+ALL_MODULES = sorted(__list_all_modules())
+log.info("Modules loaded: %s", str(ALL_MODULES))
+__all__ = ALL_MODULES + ["ALL_MODULES"]

+ 8 - 0
wbb/modules/__main__.py

@@ -0,0 +1,8 @@
+# Group for filters
+# group=1 = Check flood
+# group=2 = Filter
+# group=3 = locks
+# group=4 = Blacklist
+# group=5 = Blacklist sticker
+# group=6 = AFK & reply AFK
+# group=10 = logging users

+ 11 - 0
wbb/modules/commit.py

@@ -0,0 +1,11 @@
+from wbb import app
+from pyrogram import filters
+from wbb.utils import cust_filter, random_line
+
+__MODULE__ = "Commit"
+__HELP__ = "A Fun little module, try /commit uwu"
+
+
+@app.on_message(cust_filter.command(commands=(["commit"])))
+async def commit(client, message):
+    await message.reply_text((await random_line('wbb/commit.txt')))

+ 14 - 0
wbb/modules/greetings.py

@@ -0,0 +1,14 @@
+from wbb import app
+from pyrogram import filters, emoji
+
+__MODULE__ = "Greetings"
+__HELP__ = "Welcomes Users to the Group"
+
+MESSAGE = "{} Welcome {}!"
+
+
+@app.on_message(filters.new_chat_members)
+async def welcome(client, message):
+    new_members = [i.mention for i in message.new_chat_members]
+    text = MESSAGE.format(emoji.CROWN, ", ".join(new_members))
+    await message.reply_text(text, disable_web_page_preview=True)

+ 43 - 0
wbb/modules/log.py

@@ -0,0 +1,43 @@
+from pyrogram import filters, types
+import re
+from wbb.utils import cust_filter, nekobin
+from wbb import app, OWNER_ID
+
+
+@app.on_message(filters.user(OWNER_ID) & cust_filter.command("log"))
+async def logs_chat(client, message):
+    keyb = types.InlineKeyboardMarkup(
+        [
+            [
+                types.InlineKeyboardButton(
+                    "Paste on Nekobin", callback_data="paste_log_nekobin"
+                )
+            ]
+        ]
+    )
+    await message.reply_document(
+        "wbb/logs/error.log", caption="✨ **Here are my Logs ~**", reply_markup=keyb
+    )
+
+
+def logs_callback(_, __, query):
+    if re.match("paste_log_nekobin", query.data):
+        return True
+
+
+logs_create = filters.create(logs_callback)
+
+
+@app.on_callback_query(logs_create)
+async def paste_log_neko(client, query):
+    if query.from_user.id == OWNER_ID:
+        f = open("wbb/logs/error.log", "r")
+        data = await nekobin.neko(f.read())
+        keyb = types.InlineKeyboardMarkup(
+            [[types.InlineKeyboardButton("pasted on nekobin", url=f"{data}")]]
+        )
+        await query.message.edit_caption("Successfully Nekofied", reply_markup=keyb)
+    else:
+        await client.answer_callback_query(
+            query.id, "'Blue Button Must Press', huh?", show_alert=True
+        )

+ 14 - 0
wbb/modules/ping.py

@@ -0,0 +1,14 @@
+from wbb import app
+import time
+from wbb.utils import cust_filter
+
+__MODULE__ = "Ping"
+__HELP__ = " - /ping - to check if I am Alive or Not"
+
+
+@app.on_message(cust_filter.command(commands=(["ping"])))
+async def ping(client, message):
+    start_time = int(round(time.time() * 1000))
+    m = await message.reply_text("Starting Ping")
+    end_time = int(round(time.time() * 1000))
+    await m.edit(f"{end_time - start_time} ms")

+ 17 - 0
wbb/modules/stats.py

@@ -0,0 +1,17 @@
+from wbb import app, OWNER_ID, bot_start_time
+import time
+from pyrogram import filters
+import psutil
+from wbb.utils import cust_filter, formater
+
+__MODULE__ = "Stats"
+__HELP__ = "For Bot Owner to Check system Status"
+
+
+@app.on_message(filters.user(OWNER_ID) & cust_filter.command(commands=(["stats"])))
+async def stats(client, message):
+    cpu = psutil.cpu_percent(interval=0.5)
+    mem = psutil.virtual_memory().percent
+    disk = psutil.disk_usage("/").percent
+    stats = f"<b>Uptime:</b> {formater.get_readable_time((time.time() - bot_start_time))}" f"\n<b>CPU:</b> {cpu}% " f"\n<b>RAM:</b> {mem}% " f"\n<b>Disk:</b> {disk}%"
+    await message.reply_text(stats)

+ 0 - 4
wbb/sample_config.ini

@@ -1,4 +0,0 @@
-[pyrogram]
-bot_token=
-api_id =
-api_hash =

+ 3 - 0
wbb/utils/__init__.py

@@ -0,0 +1,3 @@
+from .read_lines import random_line
+from .botinfo import get_info
+from .misc import paginate_modules

+ 16 - 0
wbb/utils/botinfo.py

@@ -0,0 +1,16 @@
+BOT_ID = 0
+BOT_NAME = ""
+BOT_USERNAME = ""
+BOT_DC_ID = 0
+
+
+async def get_info(app):
+    global BOT_ID, BOT_NAME, BOT_USERNAME, BOT_DC_ID
+    getme = await app.get_me()
+    BOT_ID = getme.id
+    if getme.last_name:
+        BOT_NAME = getme.first_name + " " + getme.last_name
+    else:
+        BOT_NAME = getme.first_name
+    BOT_USERNAME = getme.username
+    BOT_DC_ID = getme.dc_id

+ 65 - 0
wbb/utils/cust_filter.py

@@ -0,0 +1,65 @@
+import shlex
+from typing import List
+import re
+
+from pyrogram.filters import create
+from pyrogram.types import Message
+
+from wbb import Command
+from wbb.utils.botinfo import BOT_USERNAME
+
+
+def command(
+    commands: str or List[str],
+    prefixes: str or List[str] = "/",
+    case_sensitive: bool = False,
+):
+    """
+    This is a drop in replacement for the default cust_filter.command that is included
+    in Pyrogram. The Pyrogram one does not support /command@botname type commands,
+    so this custom filter enables that throughout all groups and private chats.
+    This filter works exactly the same as the original command filter even with support for multiple command
+    prefixes and case sensitivity.
+    Command arguments are given to user as message.command
+    """
+
+    async def func(flt, _, message: Message):
+        text: str = message.text or message.caption
+        message.command = None
+
+        if not text:
+            return False
+
+        regex = "^({prefix})+\\b({regex})\\b(\\b@{bot_name}\\b)?(.*)".format(
+            prefix="|".join(re.escape(x) for x in Command),
+            regex="|".join(flt.commands).lower(),
+            bot_name=BOT_USERNAME,
+        )
+
+        matches = re.search(re.compile(regex), text.lower())
+        if matches:
+            message.command = [matches.group(2)]
+            try:
+                for arg in shlex.split(matches.group(4).strip()):
+                    if arg == BOT_USERNAME:
+                        continue
+                    message.command.append(arg)
+            except ValueError:
+                return True
+            return True
+        return False
+
+    commands = commands if type(commands) is list else [commands]
+    commands = {c if case_sensitive else c.lower() for c in commands}
+
+    prefixes = [] if prefixes is None else prefixes
+    prefixes = prefixes if type(prefixes) is list else [prefixes]
+    prefixes = set(prefixes) if prefixes else {""}
+
+    return create(
+        func,
+        "CustomCommandFilter",
+        commands=commands,
+        prefixes=prefixes,
+        case_sensitive=case_sensitive,
+    )

+ 177 - 0
wbb/utils/formater.py

@@ -0,0 +1,177 @@
+import time, math
+
+
+def time_formatter(seconds):
+    minutes, seconds = divmod(seconds, 60)
+    hours, minutes = divmod(minutes, 60)
+    days, hours = divmod(hours, 24)
+    tmp = (
+        ((str(days) + " day(s), ") if days else "")
+        + ((str(hours) + " hour(s), ") if hours else "")
+        + ((str(minutes) + " minute(s), ") if minutes else "")
+        + ((str(seconds) + " second(s), ") if seconds else "")
+    )
+    return tmp[:-2]
+
+
+def time_parser(start, end):
+    time_end = end - start
+    month = time_end // 2678400
+    days = time_end // 86400
+    hours = time_end // 3600 % 24
+    minutes = time_end // 60 % 60
+    seconds = time_end % 60
+    times = ""
+    if month:
+        times += "{} month, ".format(month)
+    if days:
+        times += "{} days, ".format(days)
+    if hours:
+        times += "{} hours, ".format(hours)
+    if minutes:
+        times += "{} minutes, ".format(minutes)
+    if seconds:
+        times += "{} seconds".format(seconds)
+    if times == "":
+        times = "{} miliseconds".format(time_end)
+    return times
+
+
+def time_parser_int(time_end):
+    month = time_end // 2678400
+    days = time_end // 86400
+    hours = time_end // 3600 % 24
+    minutes = time_end // 60 % 60
+    seconds = time_end % 60
+    times = ""
+    if month:
+        times += "{} month, ".format(month)
+    if days:
+        times += "{} days, ".format(days)
+    if hours:
+        times += "{} hours, ".format(hours)
+    if minutes:
+        times += "{} minutes, ".format(minutes)
+    if seconds:
+        times += "{} seconds".format(seconds)
+    if times == "":
+        times = "{} miliseconds".format(time_end)
+    return times[:-2] if times[-2:] == ", " else times
+
+
+def get_readable_time(seconds: int) -> str:
+    count = 0
+    ping_time = ""
+    time_list = []
+    time_suffix_list = ["s", "m", "h", "days"]
+    while count < 4:
+        count += 1
+        remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24)
+        if seconds == 0 and remainder == 0:
+            break
+        time_list.append(int(result))
+        seconds = int(remainder)
+    for x in range(len(time_list)):
+        time_list[x] = str(time_list[x]) + time_suffix_list[x]
+    if len(time_list) == 4:
+        ping_time += time_list.pop() + ", "
+    time_list.reverse()
+    ping_time += ":".join(time_list)
+    return ping_time
+
+
+def convert_size(size_bytes):
+    if size_bytes == 0:
+        return "0B"
+    size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
+    i = int(math.floor(math.log(size_bytes, 1024)))
+    p = math.pow(1024, i)
+    s = round(size_bytes / p, 2)
+    return "%s %s" % (s, size_name[i])
+
+
+def extract_time(time_val):
+    if not any(time_val.endswith(unit) for unit in ("m", "h", "d")):
+        return False
+    unit = time_val[-1]
+    time_num = time_val[:-1]  # type: str
+    if not time_num.isdigit():
+        return False
+
+    if unit == "d":
+        bantime = int(time.time() + int(time_num) * 24 * 60 * 60)
+    elif unit == "h":
+        bantime = int(time.time() + int(time_num) * 60 * 60)
+    elif unit == "m":
+        bantime = int(time.time() + int(time_num) * 60)
+    else:
+        return False
+    return bantime
+
+
+def make_time(time_val):
+    if int(time_val) == 0:
+        return "0"
+    if int(time_val) <= 3600:
+        bantime = str(int(time_val / 60)) + "m"
+    elif int(time_val) >= 3600 and time_val <= 86400:
+        bantime = str(int(time_val / 60 / 60)) + "h"
+    elif int(time_val) >= 86400:
+        bantime = str(int(time_val / 24 / 60 / 60)) + "d"
+    return bantime
+
+
+def extract_time_set(time_val):
+    if not any(time_val.endswith(unit) for unit in ("m", "h", "d")):
+        return False
+    unit = time_val[-1]
+    time_num = time_val[:-1]  # type: str
+    if not time_num.isdigit():
+        return False
+
+    if unit == "d":
+        bantime = int(int(time_num) * 24 * 60 * 60)
+    elif unit == "h":
+        bantime = int(int(time_num) * 60 * 60)
+    elif unit == "m":
+        bantime = int(int(time_num) * 60)
+    else:
+        return False
+    return bantime
+
+
+def escape_invalid_curly_brackets(text, valids):  # sourcery skip
+    new_text = ""
+    idx = 0
+    while idx < len(text):
+        if text[idx] == "{":
+            if idx + 1 < len(text) and text[idx + 1] == "{":
+                idx += 2
+                new_text += "{{{{"
+                continue
+            else:
+                success = False
+                for v in valids:
+                    if text[idx:].startswith("{" + v + "}"):
+                        success = True
+                        break
+                if success:
+                    new_text += text[idx : idx + len(v) + 2]
+                    idx += len(v) + 2
+                    continue
+                else:
+                    new_text += "{{"
+
+        elif text[idx] == "}":
+            if idx + 1 < len(text) and text[idx + 1] == "}":
+                idx += 2
+                new_text += "}}}}"
+                continue
+            else:
+                new_text += "}}"
+
+        else:
+            new_text += text[idx]
+        idx += 1
+
+    return new_text

+ 85 - 0
wbb/utils/misc.py

@@ -0,0 +1,85 @@
+import re
+
+# import requests
+from math import ceil
+
+from pyrogram.types import InlineKeyboardButton
+from wbb import MOD_LOAD, MOD_NOLOAD
+
+
+def cleanhtml(raw_html):
+    cleanr = re.compile("<.*?>")
+    return re.sub(cleanr, "", raw_html)
+
+
+class EqInlineKeyboardButton(InlineKeyboardButton):
+    def __eq__(self, other):
+        return self.text == other.text
+
+    def __lt__(self, other):
+        return self.text < other.text
+
+    def __gt__(self, other):
+        return self.text > other.text
+
+
+def paginate_modules(page_n, module_dict, prefix, chat=None):
+    if not chat:
+        modules = sorted(
+            [
+                EqInlineKeyboardButton(
+                    x.__MODULE__,
+                    callback_data="{}_module({})".format(prefix, x.__MODULE__.lower()),
+                )
+                for x in module_dict.values()
+            ]
+        )
+    else:
+        modules = sorted(
+            [
+                EqInlineKeyboardButton(
+                    x.__MODULE__,
+                    callback_data="{}_module({},{})".format(
+                        prefix, chat, x.__MODULE__.lower()
+                    ),
+                )
+                for x in module_dict.values()
+            ]
+        )
+
+    pairs = list(zip(modules[::3], modules[1::3], modules[2::3]))
+    c = 0
+    for x in pairs:
+        for _ in x:
+            c += 1
+    if len(modules) - c == 1:
+        pairs.append((modules[-1],))
+    elif len(modules) - c == 2:
+        pairs.append(
+            (
+                modules[-2],
+                modules[-1],
+            )
+        )
+
+    max_num_pages = ceil(len(pairs) / 7)
+    modulo_page = page_n % max_num_pages
+
+    # can only have a certain amount of buttons side by side
+    if len(pairs) > 7:
+        pairs = pairs[modulo_page * 7 : 7 * (modulo_page + 1)] + [
+            (
+                EqInlineKeyboardButton(
+                    "<", callback_data="{}_prev({})".format(prefix, modulo_page)
+                ),
+                EqInlineKeyboardButton(
+                    ">", callback_data="{}_next({})".format(prefix, modulo_page)
+                ),
+            )
+        ]
+
+    return pairs
+
+
+def is_module_loaded(name):
+    return (not MOD_LOAD or name in MOD_LOAD) and name not in MOD_NOLOAD

+ 12 - 0
wbb/utils/nekobin.py

@@ -0,0 +1,12 @@
+import aiohttp
+
+
+async def neko(data):
+    BASE_URL = "https://nekobin.com"
+    async with aiohttp.ClientSession() as session:
+        async with session.post(
+            f"{BASE_URL}/api/documents", json={"content": data}, timeout=3
+        ) as response:
+            key = (await response.json())["result"]["key"]
+            reply = f"{BASE_URL}/{key}"
+    return reply

+ 5 - 0
wbb/utils/read_lines.py

@@ -0,0 +1,5 @@
+import random
+
+
+async def random_line(fname):
+    return random.choice(open(fname).read().splitlines())

+ 0 - 50
wbb/wbb.py

@@ -1,50 +0,0 @@
-from pyrogram import Client, filters, emoji, types
-import random
-import time
-import shutil, psutil
-
-app = Client("WilliamButcher")
-
-
-def random_line(fname):
-    lines = open(fname).read().splitlines()
-    return random.choice(lines)
-
-@app.on_message(filters.command(commands = (["start"])))
-async def start(client, message):
-    await message.reply_text(random_line('wbb/start.txt'))
-
-@app.on_message(filters.command(commands = (["commit"])))
-async def commit(client, message):
-    await message.reply_text(random_line('wbb/commit.txt'))
-
-
-@app.on_message(filters.command(commands = (["ping"])))
-def ping(client, message):
-    start_time = int(round(time.time() * 1000))
-    message.reply_text("Starting Ping")
-    end_time = int(round(time.time() * 1000))
-    message.reply_text(f'{end_time - start_time} ms')     #Need to edit the above message instead of sending another
-
-
-@app.on_message(filters.command(commands =(["stats"])))
-def stats(client, message):
-    cpu = psutil.cpu_percent(interval=0.5)
-    mem = psutil.virtual_memory().percent
-    disk = psutil.disk_usage('/').percent
-    stats = f'<b>CPU:</b> {cpu}% ' \
-            f'<b>RAM:</b> {mem}% ' \
-            f'<b>Disk:</b> {disk}%'
-    message.reply_text(stats)
-
-
-
-
-MESSAGE = "{} Welcome {}!" 
-
-@app.on_message(filters.new_chat_members)
-def welcome(client, message):
-    new_members = [i.mention for i in message.new_chat_members]
-    text = MESSAGE.format(emoji.CROWN, ", ".join(new_members))
-    message.reply_text(text, disable_web_page_preview=True)
-