├── .deepsource.toml ├── .dockerignore ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── assets └── arial.ttf ├── heroku.yml ├── nezuko ├── __init__.py ├── __main__.py ├── core │ ├── decorators │ │ ├── errors.py │ │ ├── misc.py │ │ └── permissions.py │ ├── filters.py │ ├── keyboard.py │ ├── sections.py │ ├── tasks.py │ └── types │ │ ├── InlineQueryResult.py │ │ └── __init__.py ├── modules │ ├── __init__.py │ ├── __main__.py │ ├── admin.py │ ├── admin_misc.py │ ├── animals.py │ ├── anime.py │ ├── anime_picks.py │ ├── antiservice.py │ ├── autocorrect.py │ ├── blacklist.py │ ├── blacklist_chat.py │ ├── carbon.py │ ├── cat.py │ ├── chat_watcher.py │ ├── chatbot.py │ ├── couple.py │ ├── cricinfo.py │ ├── crypto.py │ ├── dice.py │ ├── filters.py │ ├── flood.py │ ├── fun.py │ ├── global_stats.py │ ├── greetings.py │ ├── imdb.py │ ├── img_pdf.py │ ├── info.py │ ├── inline.py │ ├── karma.py │ ├── locks.py │ ├── misc.py │ ├── mongo_backup.py │ ├── music.py │ ├── notes.py │ ├── parse_preview.py │ ├── paste.py │ ├── pipes.py │ ├── proxy.py │ ├── quotly.py │ ├── reddit.py │ ├── regex.py │ ├── repo.py │ ├── reverse.py │ ├── rss.py │ ├── stickers.py │ ├── sudo.py │ ├── sudoers.py │ ├── telegraph.py │ ├── tts.py │ ├── urltools.py │ └── webss.py └── utils │ ├── __init__.py │ ├── constants.py │ ├── dbfunctions.py │ ├── downloader.py │ ├── files.py │ ├── filter_groups.py │ ├── formatter.py │ ├── fun_strings.py │ ├── functions.py │ ├── http.py │ ├── inlinefuncs.py │ ├── json_prettify.py │ ├── misc.py │ ├── pastebin.py │ ├── read_lines.py │ ├── rss.py │ ├── runs.txt │ └── stickerset.py ├── requirements.txt ├── sample_config.env └── sample_config.py /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "python" 5 | enabled = true 6 | 7 | [analyzers.meta] 8 | runtime_version = "3.x.x" 9 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .webm 2 | *.mp3 3 | *.mp4 4 | *.webp 5 | *.weba 6 | *.part 7 | *.ytdl 8 | final.png 9 | background.png 10 | temp.png 11 | # python 12 | venv/ 13 | __pycache__/ 14 | # tgbot 15 | config.py 16 | *.session 17 | *.session-journal 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.env 2 | *.m4a 3 | *.apk 4 | spam_mod.py 5 | *.c 6 | dump 7 | exec 8 | *.out 9 | __pycache__ 10 | *.opus 11 | unknown_errors.txt 12 | *.jpg 13 | token.py 14 | *.raw 15 | images/ 16 | # Byte-compiled / optimized / DLL files 17 | __pycache__/ 18 | *.py[cod] 19 | *.png 20 | *.mp4 21 | *.webm 22 | *$py.class 23 | *.mp3 24 | . 25 | # C extensions 26 | *.so 27 | config.py 28 | # Distribution / packaging 29 | .Python 30 | build/ 31 | develop-eggs/ 32 | dist/ 33 | downloads/ 34 | eggs/ 35 | .eggs/ 36 | lib/ 37 | lib64/ 38 | parts/ 39 | sdist/ 40 | var/ 41 | wheels/ 42 | pip-wheel-metadata/ 43 | share/python-wheels/ 44 | *.egg-info/ 45 | .installed.cfg 46 | *.egg 47 | MANIFEST 48 | 49 | # PyInstaller 50 | # Usually these files are written by a python script from a template 51 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 52 | *.manifest 53 | *.spec 54 | 55 | # Installer logs 56 | pip-log.txt 57 | pip-delete-this-directory.txt 58 | 59 | # Unit test / coverage reports 60 | htmlcov/ 61 | .tox/ 62 | .nox/ 63 | .coverage 64 | .coverage.* 65 | .cache 66 | nosetests.xml 67 | coverage.xml 68 | *.cover 69 | *.py,cover 70 | .hypothesis/ 71 | .pytest_cache/ 72 | 73 | # Translations 74 | *.mo 75 | *.pot 76 | 77 | # Django stuff: 78 | *.log 79 | local_settings.py 80 | db.sqlite3 81 | db.sqlite3-journal 82 | 83 | # Flask stuff: 84 | instance/ 85 | .webassets-cache 86 | 87 | # Scrapy stuff: 88 | .scrapy 89 | 90 | # Sphinx documentation 91 | docs/_build/ 92 | 93 | # PyBuilder 94 | target/ 95 | 96 | # Jupyter Notebook 97 | .ipynb_checkpoints 98 | 99 | # IPython 100 | profile_default/ 101 | ipython_config.py 102 | 103 | # pyenv 104 | .python-version 105 | 106 | # pipenv 107 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 108 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 109 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 110 | # install all needed dependencies. 111 | #Pipfile.lock 112 | 113 | # celery beat schedule file 114 | celerybeat-schedule 115 | 116 | # SageMath parsed files 117 | *.sage.py 118 | 119 | # Environments 120 | .env 121 | .venv 122 | env/ 123 | venv/ 124 | ENV/ 125 | env.bak/ 126 | venv.bak/ 127 | 128 | # Spyder project settings 129 | .spyderproject 130 | .spyproject 131 | 132 | # Rope project settings 133 | .ropeproject 134 | 135 | # mkdocs documentation 136 | /site 137 | 138 | # mypy 139 | .mypy_cache/ 140 | .dmypy.json 141 | dmypy.json 142 | 143 | # Pyre type checker 144 | .pyre/ 145 | *.ini 146 | *.pyc 147 | *.session 148 | *.session-journal 149 | 150 | # vim 151 | [._]*.sw[a-p] 152 | 153 | #others 154 | neofetch.txt 155 | error.log 156 | permissions.json 157 | .vscode 158 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at thehamkercat@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | * Using welcoming and inclusive language 36 | * Being respectful of differing viewpoints and experiences 37 | * Gracefully accepting constructive criticism 38 | * Focusing on what is best for the community 39 | * Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | * The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | * Trolling, insulting/derogatory comments, and personal or political attacks 46 | * Public or private harassment 47 | * Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | * Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ 93 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM archlinux/archlinux:latest 2 | 3 | # Install dependencies 4 | RUN pacman -Syu --noconfirm && pacman -S --noconfirm git wget libxml2 libxslt zip python-pip ffmpeg 5 | 6 | # Downloading mongodb tools 7 | RUN wget https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2004-x86_64-100.5.2.tgz && tar -xf mongodb*.tgz && \ 8 | mv mongodb-database-tools-ubuntu2004-x86_64-100.5.2/bin/* /bin/ && \ 9 | rm -rf mongodb-database-tools-ubuntu2004-x86_64-100.5.2* 10 | 11 | # Changing working directory and it's permission 12 | WORKDIR /app 13 | RUN chmod 777 /app 14 | 15 | # Install python dependencies 16 | COPY requirements.txt . 17 | RUN pip3 install --no-cache-dir -U -r requirements.txt 18 | 19 | # Copy files to the working directory 20 | COPY . . 21 | 22 | # Run the application 23 | CMD ["python3","-m","nezuko"] 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TheHamkerCat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python3 -m nezuko 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
29 | A ready-to-use running instance of this bot can be found on Telegram
30 | NezukoBot
31 |
38 | Python3.9 | 39 | Telegram API Key | 40 | Telegram Bot Token | 41 | MongoDB URI 42 |
43 | 44 |" + match.get_text() + "
\n\n" for match in result
48 | )
49 | return await message.reply(Sed, parse_mode=ParseMode.HTML)
50 |
--------------------------------------------------------------------------------
/nezuko/modules/crypto.py:
--------------------------------------------------------------------------------
1 | from pyrogram import filters
2 |
3 | from nezuko import app
4 | from nezuko.core.decorators.errors import capture_err
5 | from nezuko.core.keyboard import ikb
6 | from nezuko.core.sections import section
7 | from nezuko.utils.http import get
8 |
9 | __MODULE__ = "Crypto"
10 | __HELP__ = """
11 | /crypto [currency]
12 | Get Real Time value from currency given.
13 | """
14 |
15 |
16 | @app.on_message(filters.command("crypto"))
17 | @capture_err
18 | async def crypto(_, message):
19 | if len(message.command) < 2:
20 | return await message.reply("/crypto [currency]")
21 |
22 | currency = message.text.split(None, 1)[1].lower()
23 |
24 | btn = ikb(
25 | {"Available Currencies": "https://plotcryptoprice.herokuapp.com"},
26 | )
27 |
28 | m = await message.reply("`Processing...`")
29 |
30 | try:
31 | r = await get(
32 | "https://x.wazirx.com/wazirx-falcon/api/v2.0/crypto_rates",
33 | timeout=5,
34 | )
35 | except Exception:
36 | return await m.edit("[ERROR]: Something went wrong.")
37 |
38 | if currency not in r:
39 | return await m.edit(
40 | "[ERROR]: INVALID CURRENCY",
41 | reply_markup=btn,
42 | )
43 |
44 | body = {i.upper(): j for i, j in r.get(currency).items()}
45 |
46 | text = section(
47 | "Current Crypto Rates For " + currency.upper(),
48 | body,
49 | )
50 | await m.edit(text, reply_markup=btn)
51 |
--------------------------------------------------------------------------------
/nezuko/modules/dice.py:
--------------------------------------------------------------------------------
1 | from pyrogram import filters
2 | from pyrogram.types import Message
3 |
4 | from nezuko import SUDOERS, app
5 |
6 | __MODULE__ = "Dice"
7 | __HELP__ = """
8 | /dice
9 | Roll a dice.
10 | """
11 |
12 |
13 | @app.on_message(filters.command("dice"))
14 | async def throw_dice(client, message: Message):
15 | six = (message.from_user.id in SUDOERS) if message.from_user else False
16 |
17 | c = message.chat.id
18 | if not six:
19 | return await client.send_dice(c, "🎲")
20 |
21 | m = await client.send_dice(c, "🎲")
22 |
23 | while m.dice.value != 6:
24 | await m.delete()
25 | m = await client.send_dice(c, "🎲")
26 |
--------------------------------------------------------------------------------
/nezuko/modules/filters.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | import re
25 |
26 | from pyrogram import filters
27 |
28 | from nezuko import app
29 | from nezuko.core.decorators.errors import capture_err
30 | from nezuko.core.decorators.permissions import adminsOnly
31 | from nezuko.core.keyboard import ikb
32 | from nezuko.utils.dbfunctions import (
33 | delete_filter,
34 | get_filter,
35 | get_filters_names,
36 | save_filter,
37 | )
38 | from nezuko.utils.filter_groups import chat_filters_group
39 | from nezuko.utils.functions import extract_text_and_keyb
40 |
41 | __MODULE__ = "Filters"
42 | __HELP__ = """/filters To Get All The Filters In The Chat.
43 | /filter [FILTER_NAME] To Save A Filter (Can be a sticker or text).
44 | /stop [FILTER_NAME] To Stop A Filter.
45 |
46 |
47 | You can use markdown or html to save text too.
48 |
49 | Checkout /markdownhelp to know more about formattings and other syntax.
50 | """
51 |
52 |
53 | @app.on_message(filters.command("filter") & ~filters.private)
54 | @adminsOnly("can_change_info")
55 | async def save_filters(_, message):
56 | if len(message.command) < 2 or not message.reply_to_message:
57 | return await message.reply_text(
58 | "**Usage:**\nReply to a text or sticker with /filter [FILTER_NAME] to save it."
59 | )
60 | if (
61 | not message.reply_to_message.text
62 | and not message.reply_to_message.sticker
63 | ):
64 | return await message.reply_text(
65 | "__**You can only save text or stickers in filters.**__"
66 | )
67 | name = message.text.split(None, 1)[1].strip()
68 | if not name:
69 | return await message.reply_text(
70 | "**Usage:**\n__/filter [FILTER_NAME]__"
71 | )
72 | chat_id = message.chat.id
73 | _type = "text" if message.reply_to_message.text else "sticker"
74 | _filter = {
75 | "type": _type,
76 | "data": message.reply_to_message.text.markdown
77 | if _type == "text"
78 | else message.reply_to_message.sticker.file_id,
79 | }
80 | await save_filter(chat_id, name, _filter)
81 | await message.reply_text(f"__**Saved filter {name}.**__")
82 |
83 |
84 | @app.on_message(filters.command("filters") & ~filters.private)
85 | @capture_err
86 | async def get_filterss(_, message):
87 | _filters = await get_filters_names(message.chat.id)
88 | if not _filters:
89 | return await message.reply_text("**No filters in this chat.**")
90 | _filters.sort()
91 | msg = f"List of filters in {message.chat.title}\n"
92 | for _filter in _filters:
93 | msg += f"**-** `{_filter}`\n"
94 | await message.reply_text(msg)
95 |
96 |
97 | @app.on_message(filters.command("stop") & ~filters.private)
98 | @adminsOnly("can_change_info")
99 | async def del_filter(_, message):
100 | if len(message.command) < 2:
101 | return await message.reply_text("**Usage:**\n__/stop [FILTER_NAME]__")
102 | name = message.text.split(None, 1)[1].strip()
103 | if not name:
104 | return await message.reply_text("**Usage:**\n__/stop [FILTER_NAME]__")
105 | chat_id = message.chat.id
106 | deleted = await delete_filter(chat_id, name)
107 | if deleted:
108 | await message.reply_text(f"**Deleted filter {name}.**")
109 | else:
110 | await message.reply_text("**No such filter.**")
111 |
112 |
113 | @app.on_message(
114 | filters.text & ~filters.private & ~filters.via_bot & ~filters.forwarded,
115 | group=chat_filters_group,
116 | )
117 | @capture_err
118 | async def filters_re(_, message):
119 | text = message.text.lower().strip()
120 | if not text:
121 | return
122 | chat_id = message.chat.id
123 | list_of_filters = await get_filters_names(chat_id)
124 | for word in list_of_filters:
125 | pattern = r"( |^|[^\w])" + re.escape(word) + r"( |$|[^\w])"
126 | if re.search(pattern, text, flags=re.IGNORECASE):
127 | _filter = await get_filter(chat_id, word)
128 | data_type = _filter["type"]
129 | data = _filter["data"]
130 | if data_type == "text":
131 | keyb = None
132 | if re.findall(r"\[.+\,.+\]", data):
133 | keyboard = extract_text_and_keyb(ikb, data)
134 | if keyboard:
135 | data, keyb = keyboard
136 |
137 | if message.reply_to_message:
138 | await message.reply_to_message.reply_text(
139 | data,
140 | reply_markup=keyb,
141 | disable_web_page_preview=True,
142 | )
143 |
144 | if text.startswith("~"):
145 | await message.delete()
146 | return
147 |
148 | return await message.reply_text(
149 | data,
150 | reply_markup=keyb,
151 | disable_web_page_preview=True,
152 | )
153 | if message.reply_to_message:
154 | await message.reply_to_message.reply_sticker(data)
155 |
156 | if text.startswith("~"):
157 | await message.delete()
158 | return
159 | return await message.reply_sticker(data)
160 |
--------------------------------------------------------------------------------
/nezuko/modules/flood.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from asyncio import get_running_loop, sleep
25 | from time import time
26 |
27 | from pyrogram import filters
28 | from pyrogram.types import (
29 | CallbackQuery,
30 | ChatPermissions,
31 | InlineKeyboardButton,
32 | InlineKeyboardMarkup,
33 | Message,
34 | )
35 |
36 | from nezuko import SUDOERS, app
37 | from nezuko.core.decorators.errors import capture_err
38 | from nezuko.core.decorators.permissions import adminsOnly
39 | from nezuko.modules.admin import list_admins, member_permissions
40 | from nezuko.utils.dbfunctions import flood_off, flood_on, is_flood_on
41 | from nezuko.utils.filter_groups import flood_group
42 |
43 | __MODULE__ = "Flood"
44 | __HELP__ = """
45 | Anti-Flood system, the one who sends more than 10 messages in a row, gets muted for an hour (Except for admins).
46 |
47 | /flood [ENABLE|DISABLE] - Turn flood detection on or off
48 | """
49 |
50 |
51 | DB = {} # TODO Use mongodb instead of a fucking dict.
52 |
53 |
54 | def reset_flood(chat_id, user_id=0):
55 | for user in DB[chat_id].keys():
56 | if user != user_id:
57 | DB[chat_id][user] = 0
58 |
59 |
60 | @app.on_message(
61 | ~filters.service
62 | & ~filters.me
63 | & ~filters.private
64 | & ~filters.channel
65 | & ~filters.bot,
66 | group=flood_group,
67 | )
68 | @capture_err
69 | async def flood_control_func(_, message: Message):
70 | if not message.chat:
71 | return
72 | chat_id = message.chat.id
73 | if not (await is_flood_on(chat_id)):
74 | return
75 | # Initialize db if not already.
76 | if chat_id not in DB:
77 | DB[chat_id] = {}
78 |
79 | if not message.from_user:
80 | reset_flood(chat_id)
81 | return
82 |
83 | user_id = message.from_user.id
84 | mention = message.from_user.mention
85 |
86 | if user_id not in DB[chat_id]:
87 | DB[chat_id][user_id] = 0
88 |
89 | # Reset floodb of current chat if some other user sends a message
90 | reset_flood(chat_id, user_id)
91 |
92 | # Ignore devs and admins
93 | mods = (await list_admins(chat_id)) + SUDOERS
94 | if user_id in mods:
95 | return
96 |
97 | # Mute if user sends more than 10 messages in a row
98 | if DB[chat_id][user_id] >= 10:
99 | DB[chat_id][user_id] = 0
100 | try:
101 | await message.chat.restrict_member(
102 | user_id,
103 | permissions=ChatPermissions(),
104 | until_date=int(time() + 3600),
105 | )
106 | except Exception:
107 | return
108 | keyboard = InlineKeyboardMarkup(
109 | [
110 | [
111 | InlineKeyboardButton(
112 | text="🚨 Unmute 🚨",
113 | callback_data=f"unmute_{user_id}",
114 | )
115 | ]
116 | ]
117 | )
118 | m = await message.reply_text(
119 | f"Imagine flooding the chat in front of me, Muted {mention} for an hour!",
120 | reply_markup=keyboard,
121 | )
122 |
123 | async def delete():
124 | await sleep(3600)
125 | try:
126 | await m.delete()
127 | except Exception:
128 | pass
129 |
130 | loop = get_running_loop()
131 | return loop.create_task(delete())
132 | DB[chat_id][user_id] += 1
133 |
134 |
135 | @app.on_callback_query(filters.regex("unmute_"))
136 | async def flood_callback_func(_, cq: CallbackQuery):
137 | from_user = cq.from_user
138 | permissions = await member_permissions(cq.message.chat.id, from_user.id)
139 | permission = "can_restrict_members"
140 | if permission not in permissions:
141 | return await cq.answer(
142 | "You don't have enough permissions to perform this action.\n"
143 | + f"Permission needed: {permission}",
144 | show_alert=True,
145 | )
146 | user_id = cq.data.split("_")[1]
147 | await cq.message.chat.unban_member(user_id)
148 | text = cq.message.text.markdown
149 | text = f"~~{text}~~\n\n"
150 | text += f"__User unmuted by {from_user.mention}__"
151 | await cq.message.edit(text)
152 |
153 |
154 | @app.on_message(filters.command("flood") & ~filters.private)
155 | @adminsOnly("can_change_info")
156 | async def flood_toggle(_, message: Message):
157 | if len(message.command) != 2:
158 | return await message.reply_text("Usage: /flood [ENABLE|DISABLE]")
159 | status = message.text.split(None, 1)[1].strip()
160 | status = status.lower()
161 | chat_id = message.chat.id
162 | if status == "enable":
163 | await flood_on(chat_id)
164 | await message.reply_text("Enabled Flood Checker.")
165 | elif status == "disable":
166 | await flood_off(chat_id)
167 | await message.reply_text("Disabled Flood Checker.")
168 | else:
169 | await message.reply_text("Unknown Suffix, Use /flood [ENABLE|DISABLE]")
170 |
--------------------------------------------------------------------------------
/nezuko/modules/fun.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | from pyrogram import filters
4 | from pyrogram.types import Message
5 |
6 | import nezuko.utils.fun_strings as fun_strings
7 | from nezuko import BOT_ID, BOT_NAME, BOT_USERNAME, SUDOERS, app, arq
8 | from nezuko.core.decorators.errors import capture_err
9 |
10 | __MODULE__ = "Fun"
11 | __HELP__ = """
12 | /weebify - To weebify a message.
13 | /wish - To get succession rate! (Just for fun-.-)
14 | /slap - To slap someone.
15 |
16 | """
17 |
18 |
19 | normiefont = [
20 | "a",
21 | "b",
22 | "c",
23 | "d",
24 | "e",
25 | "f",
26 | "g",
27 | "h",
28 | "i",
29 | "j",
30 | "k",
31 | "l",
32 | "m",
33 | "n",
34 | "o",
35 | "p",
36 | "q",
37 | "r",
38 | "s",
39 | "t",
40 | "u",
41 | "v",
42 | "w",
43 | "x",
44 | "y",
45 | "z",
46 | ]
47 | weebyfont = [
48 | "卂",
49 | "乃",
50 | "匚",
51 | "刀",
52 | "乇",
53 | "下",
54 | "厶",
55 | "卄",
56 | "工",
57 | "丁",
58 | "长",
59 | "乚",
60 | "从",
61 | "𠘨",
62 | "口",
63 | "尸",
64 | "㔿",
65 | "尺",
66 | "丂",
67 | "丅",
68 | "凵",
69 | "リ",
70 | "山",
71 | "乂",
72 | "丫",
73 | "乙",
74 | ]
75 |
76 |
77 | def weebifytext(text):
78 | string = text.lower().replace(" ", " ")
79 | for normiecharacter in string:
80 | if normiecharacter in normiefont:
81 | weebycharacter = weebyfont[normiefont.index(normiecharacter)]
82 | string = string.replace(normiecharacter, weebycharacter)
83 | return string
84 |
85 |
86 | @app.on_message(filters.command(["weebify", f"weebify@{BOT_USERNAME}"]))
87 | @capture_err
88 | async def weebify(client, message: Message):
89 | if message.reply_to_message:
90 | return await message.reply_text(
91 | weebifytext(message.reply_to_message.text)
92 | )
93 | if len(message.command) < 2:
94 | return await message.reply_text(
95 | "reply **/weebify** To a message for weebify or use **/weebify Your Text**"
96 | )
97 | message.command.pop(0)
98 | return await message.reply_text(weebifytext(" ".join(message.command)))
99 |
100 |
101 | @app.on_message(
102 | filters.command(["slap", f"slap@{BOT_USERNAME}"]) & ~filters.private
103 | )
104 | @capture_err
105 | async def slap(client, message: Message):
106 | if message.reply_to_message:
107 | try:
108 | if message.reply_to_message.from_user.id == BOT_ID:
109 | return await message.reply_text(
110 | "Stop slapping me. REEEEEEEEEEEEEE."
111 | )
112 | except:
113 | return await message.reply_text(
114 | "You Cann't Slap an Anon Admin. :p"
115 | )
116 | else:
117 | try:
118 | user1 = message.from_user.first_name
119 | except:
120 | user1 = message.chat.title
121 | try:
122 | user2 = message.reply_to_message.from_user.first_name
123 | except:
124 | user2 = message.chat.title
125 | temp = random.choice(fun_strings.SLAP_TEMPLATES)
126 | item = random.choice(fun_strings.ITEMS)
127 | hit = random.choice(fun_strings.HIT)
128 | throw = random.choice(fun_strings.THROW)
129 | reply = temp.format(
130 | user1=user1, user2=user2, item=item, hits=hit, throws=throw
131 | )
132 | return await message.reply_text(
133 | reply, reply_to_message_id=message.reply_to_message.id
134 | )
135 | else:
136 | user1 = BOT_NAME
137 | try:
138 | user2 = message.from_user.first_name
139 | except:
140 | user2 = message.chat.title
141 | temp = random.choice(fun_strings.SLAP_TEMPLATES)
142 | item = random.choice(fun_strings.ITEMS)
143 | hit = random.choice(fun_strings.HIT)
144 | throw = random.choice(fun_strings.THROW)
145 | reply = temp.format(
146 | user1=user1, user2=user2, item=item, hits=hit, throws=throw
147 | )
148 | return await message.reply_text(reply)
149 |
150 |
151 | @app.on_message(filters.command(["wish", f"wish@{BOT_USERNAME}"]))
152 | @capture_err
153 | async def wish(client, message: Message):
154 | if message.reply_to_message:
155 | return await message.reply_text(
156 | f"Your Wish **{message.reply_to_message.text}** Has {random.randint(1,99)}% Succession Rate!"
157 | )
158 | if len(message.command) < 2:
159 | return await message.reply_text(
160 | "reply **/wish** To a message for wish or use **/wish Your Wish**"
161 | )
162 | message.command.pop(0)
163 | return await message.reply_text(
164 | f"Your Wish **{' '.join(message.command)}** Has {random.randint(1,99)}% Succession Rate!"
165 | )
166 |
--------------------------------------------------------------------------------
/nezuko/modules/global_stats.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | import asyncio
25 |
26 | from pyrogram import filters
27 | from pyrogram.errors import FloodWait
28 |
29 | from nezuko import BOT_ID, BOT_NAME, SUDOERS, app
30 | from nezuko.core.decorators.errors import capture_err
31 | from nezuko.modules import ALL_MODULES
32 | from nezuko.utils.dbfunctions import (
33 | get_blacklist_filters_count,
34 | get_filters_count,
35 | get_gbans_count,
36 | get_karmas_count,
37 | get_notes_count,
38 | get_rss_feeds_count,
39 | get_served_chats,
40 | get_served_users,
41 | get_warns_count,
42 | remove_served_chat,
43 | )
44 | from nezuko.utils.http import get
45 | from nezuko.utils.inlinefuncs import keywords_list
46 |
47 |
48 | @app.on_message(filters.command("clean_db") & filters.user(SUDOERS))
49 | @capture_err
50 | async def clean_db(_, message):
51 | served_chats = [int(i["chat_id"]) for i in (await get_served_chats())]
52 | m = await message.reply(
53 | f"__**Cleaning database, Might take around {len(served_chats)*2} seconds.**__",
54 | )
55 | for served_chat in served_chats:
56 | try:
57 | await app.get_chat_members(served_chat, BOT_ID)
58 | await asyncio.sleep(2)
59 | except FloodWait as e:
60 | await asyncio.sleep(int(e.x))
61 | except Exception:
62 | await remove_served_chat(served_chat)
63 | served_chats.remove(served_chat)
64 | await m.edit("**Database Cleaned.**")
65 |
66 |
67 | @app.on_message(filters.command("gstats") & filters.user(SUDOERS))
68 | @capture_err
69 | async def global_stats(_, message):
70 | m = await app.send_message(
71 | message.chat.id,
72 | text="__**Analysing Stats...**__",
73 | disable_web_page_preview=True,
74 | )
75 |
76 | # For bot served chat and users count
77 | served_chats = len(await get_served_chats())
78 | served_users = len(await get_served_users())
79 | # Gbans count
80 | gbans = await get_gbans_count()
81 | _notes = await get_notes_count()
82 | notes_count = _notes["notes_count"]
83 | notes_chats_count = _notes["chats_count"]
84 |
85 | # Filters count across chats
86 | _filters = await get_filters_count()
87 | filters_count = _filters["filters_count"]
88 | filters_chats_count = _filters["chats_count"]
89 |
90 | # Blacklisted filters count across chats
91 | _filters = await get_blacklist_filters_count()
92 | blacklist_filters_count = _filters["filters_count"]
93 | blacklist_filters_chats_count = _filters["chats_count"]
94 |
95 | # Warns count across chats
96 | _warns = await get_warns_count()
97 | warns_count = _warns["warns_count"]
98 | warns_chats_count = _warns["chats_count"]
99 |
100 | # Karmas count across chats
101 | _karmas = await get_karmas_count()
102 | karmas_count = _karmas["karmas_count"]
103 | karmas_chats_count = _karmas["chats_count"]
104 |
105 | # Contributors/Developers count and commits on github
106 | url = "https://api.github.com/repos/rozari0/nezukobot/contributors"
107 | rurl = "https://github.com/rozari0/nezukobot"
108 | developers = await get(url)
109 | commits = sum(developer["contributions"] for developer in developers)
110 | developers = len(developers)
111 |
112 | # Rss feeds
113 | rss_count = await get_rss_feeds_count()
114 | # Modules info
115 | modules_count = len(ALL_MODULES)
116 |
117 | # Userbot info
118 | groups_ub = channels_ub = bots_ub = privates_ub = total_ub = 0
119 |
120 | msg = f"""
121 | **Global Stats of {BOT_NAME}**:
122 | **{modules_count}** Modules Loaded.
123 | **{len(keywords_list)}** Inline Modules Loaded.
124 | **{rss_count}** Active RSS Feeds.
125 | **{gbans}** Globally banned users.
126 | **{filters_count}** Filters, Across **{filters_chats_count}** chats.
127 | **{blacklist_filters_count}** Blacklist Filters, Across **{blacklist_filters_chats_count}** chats.
128 | **{notes_count}** Notes, Across **{notes_chats_count}** chats.
129 | **{warns_count}** Warns, Across **{warns_chats_count}** chats.
130 | **{karmas_count}** Karma, Across **{karmas_chats_count}** chats.
131 | **{served_users}** Users, Across **{served_chats}** chats.
132 | **{developers}** Developers And **{commits}** Commits On **[Github]({rurl})**.
133 |
134 | """
135 | await m.edit(msg, disable_web_page_preview=True)
136 |
--------------------------------------------------------------------------------
/nezuko/modules/imdb.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 rozari0
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from imdb import IMDb
25 | from pyrogram import filters
26 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup
27 |
28 | from nezuko import app
29 |
30 | ia = IMDb()
31 |
32 |
33 | @app.on_message(filters.command("imdb"))
34 | async def imdb(client, message):
35 | """
36 | .imdb {movie['title']}
\n\
76 | Year: {movie['year']}
\n\
77 | Rating: {movie['rating'] if 'rating' in movie else 'Not Found'}
\n\
78 | Genre: {', '.join(movie['genres'])}
\n\
79 | Runtime: {movie['runtime'][0] if 'runtime' in movie else 'Not Found'}
\n\
80 | Writers: {', '.join(_writers)}
\n\
81 | Directors: {', '.join(_directors)}
\n\
82 | Actors: {', '.join(_casts)}
\n\
83 | Language: {movie['language'] if 'language' in movie else 'Not Found'}
\n\
84 | Country: {movie['country'] if 'country' in movie else 'Not Found'}
\n\
85 | "
86 | try:
87 | m = await message.reply_photo(
88 | movie["full-size cover url"],
89 | caption=caption,
90 | reply_markup=reply_markup,
91 | )
92 | except KeyError:
93 | m = await message.reply_photo(
94 | movie["cover url"], caption=caption, reply_markup=reply_markup
95 | )
96 |
97 | return await m.reply_text(
98 | f"Plot: {movie['plot outline'] if 'plot outline' in movie else 'Not available'}
"
99 | )
100 |
--------------------------------------------------------------------------------
/nezuko/modules/img_pdf.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including witout limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from io import BytesIO
25 | from os import path, remove
26 | from time import time
27 |
28 | import img2pdf
29 | from PIL import Image
30 | from pyrogram import filters
31 | from pyrogram.types import Message
32 |
33 | from nezuko import app
34 | from nezuko.core.decorators.errors import capture_err
35 | from nezuko.core.sections import section
36 |
37 |
38 | async def convert(
39 | main_message: Message,
40 | reply_messages,
41 | status_message: Message,
42 | start_time: float,
43 | ):
44 | m = status_message
45 |
46 | documents = []
47 |
48 | for message in reply_messages:
49 | if not message.document:
50 | return await m.edit("Not document, ABORTED!")
51 |
52 | if message.document.mime_type.split("/")[0] != "image":
53 | return await m.edit("Invalid mime type!")
54 |
55 | if message.document.file_size > 5000000:
56 | return await m.edit("Size too large, ABORTED!")
57 | documents.append(await message.download())
58 |
59 | for img_path in documents:
60 | img = Image.open(img_path).convert("RGB")
61 | img.save(img_path, "JPEG", quality=100)
62 |
63 | pdf = BytesIO(img2pdf.convert(documents))
64 | pdf.name = "wbb.pdf"
65 |
66 | if len(main_message.command) >= 2:
67 | pdf.name = main_message.text.split(None, 1)[1]
68 |
69 | elapsed = round(time() - start_time, 2)
70 |
71 | await main_message.reply_document(
72 | document=pdf,
73 | caption=section(
74 | "IMG2PDF",
75 | body={
76 | "Title": pdf.name,
77 | "Size": f"{pdf.__sizeof__() / (10**6)}MB",
78 | "Pages": len(documents),
79 | "Took": f"{elapsed}s",
80 | },
81 | ),
82 | )
83 |
84 | await m.delete()
85 | pdf.close()
86 | for file in documents:
87 | if path.exists(file):
88 | remove(file)
89 |
90 |
91 | @app.on_message(filters.command("pdf"))
92 | @capture_err
93 | async def img_to_pdf(_, message: Message):
94 | reply = message.reply_to_message
95 | if not reply:
96 | return await message.reply(
97 | "Reply to an image (as document) or group of images."
98 | )
99 |
100 | m = await message.reply_text("Converting..")
101 | start_time = time()
102 |
103 | if reply.media_group_id:
104 | messages = await app.get_media_group(
105 | message.chat.id,
106 | reply.id,
107 | )
108 | return await convert(message, messages, m, start_time)
109 |
110 | return await convert(message, [reply], m, start_time)
111 |
--------------------------------------------------------------------------------
/nezuko/modules/info.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | import os
25 |
26 | from pyrogram import filters
27 | from pyrogram.types import Message
28 |
29 | from nezuko import SUDOERS, app
30 | from nezuko.core.sections import section
31 | from nezuko.utils.dbfunctions import is_gbanned_user, user_global_karma
32 |
33 | __MODULE__ = "Info"
34 | __HELP__ = """
35 | /info [USERNAME|ID] - Get info about a user.
36 | /chat_info [USERNAME|ID] - Get info about a chat.
37 | """
38 |
39 |
40 | async def get_user_info(user, already=False):
41 | if not already:
42 | user = await app.get_users(user)
43 | if not user.first_name:
44 | return ["Deleted account", None]
45 | user_id = user.id
46 | username = user.username
47 | first_name = user.first_name
48 | mention = user.mention("Link")
49 | dc_id = user.dc_id
50 | photo_id = user.photo.big_file_id if user.photo else None
51 | is_gbanned = await is_gbanned_user(user_id)
52 | is_sudo = user_id in SUDOERS
53 | karma = await user_global_karma(user_id)
54 | body = {
55 | "ID": user_id,
56 | "DC": dc_id,
57 | "Name": [first_name],
58 | "Username": [("@" + username) if username else "Null"],
59 | "Mention": [mention],
60 | "Sudo": is_sudo,
61 | "Karma": karma,
62 | "Gbanned": is_gbanned,
63 | }
64 | caption = section("User info", body)
65 | return [caption, photo_id]
66 |
67 |
68 | async def get_chat_info(chat, already=False):
69 | if not already:
70 | chat = await app.get_chat(chat)
71 | chat_id = chat.id
72 | username = chat.username
73 | title = chat.title
74 | type_ = chat.type
75 | is_scam = chat.is_scam
76 | description = chat.description
77 | members = chat.members_count
78 | is_restricted = chat.is_restricted
79 | link = f"[Link](t.me/{username})" if username else "Null"
80 | dc_id = chat.dc_id
81 | photo_id = chat.photo.big_file_id if chat.photo else None
82 | body = {
83 | "ID": chat_id,
84 | "DC": dc_id,
85 | "Type": type_,
86 | "Name": [title],
87 | "Username": [("@" + username) if username else "Null"],
88 | "Mention": [link],
89 | "Members": members,
90 | "Scam": is_scam,
91 | "Restricted": is_restricted,
92 | "Description": [description],
93 | }
94 | caption = section("Chat info", body)
95 | return [caption, photo_id]
96 |
97 |
98 | @app.on_message(filters.command("info"))
99 | async def info_func(_, message: Message):
100 | if message.reply_to_message:
101 | user = message.reply_to_message.from_user.id
102 | elif len(message.command) == 1:
103 | user = message.from_user.id
104 | else:
105 | user = message.text.split(None, 1)[1]
106 |
107 | m = await message.reply_text("Processing")
108 |
109 | try:
110 | info_caption, photo_id = await get_user_info(user)
111 | except Exception as e:
112 | return await m.edit(str(e))
113 |
114 | if not photo_id:
115 | return await m.edit(info_caption, disable_web_page_preview=True)
116 | photo = await app.download_media(photo_id)
117 |
118 | await message.reply_photo(photo, caption=info_caption, quote=False)
119 | await m.delete()
120 | os.remove(photo)
121 |
122 |
123 | @app.on_message(filters.command("chat_info"))
124 | async def chat_info_func(_, message: Message):
125 | try:
126 | if len(message.command) > 2:
127 | return await message.reply_text(
128 | "**Usage:**/chat_info [USERNAME|ID]"
129 | )
130 |
131 | if len(message.command) == 1:
132 | chat = message.chat.id
133 | elif len(message.command) == 2:
134 | chat = message.text.split(None, 1)[1]
135 |
136 | m = await message.reply_text("Processing")
137 |
138 | info_caption, photo_id = await get_chat_info(chat)
139 | if not photo_id:
140 | return await m.edit(info_caption, disable_web_page_preview=True)
141 |
142 | photo = await app.download_media(photo_id)
143 | await message.reply_photo(photo, caption=info_caption, quote=False)
144 |
145 | await m.delete()
146 | os.remove(photo)
147 | except Exception as e:
148 | await m.edit(e)
149 |
--------------------------------------------------------------------------------
/nezuko/modules/locks.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from pyrogram import filters
25 | from pyrogram.errors.exceptions.bad_request_400 import ChatNotModified
26 | from pyrogram.types import ChatPermissions
27 |
28 | from nezuko import SUDOERS, app
29 | from nezuko.core.decorators.errors import capture_err
30 | from nezuko.core.decorators.permissions import adminsOnly
31 | from nezuko.modules.admin import current_chat_permissions, list_admins
32 | from nezuko.utils.functions import get_urls_from_text
33 |
34 | __MODULE__ = "Locks"
35 | __HELP__ = """
36 | Commands: /lock | /unlock | /locks [No Parameters Required]
37 |
38 | Parameters:
39 | messages | stickers | gifs | media | games | polls
40 |
41 | inline | url | group_info | user_add | pin
42 |
43 | You can only pass the "all" parameter with /lock, not with /unlock
44 |
45 | Example:
46 | /lock all
47 | """
48 |
49 | incorrect_parameters = "Incorrect Parameters, Check Locks Section In Help."
50 | # Using disable_preview as a switch for url checker
51 | # That way we won't need an additional db to check
52 | # If url lock is enabled/disabled for a chat
53 | data = {
54 | "messages": "can_send_messages",
55 | "stickers": "can_send_stickers",
56 | "gifs": "can_send_animations",
57 | "media": "can_send_media_messages",
58 | "games": "can_send_games",
59 | "inline": "can_use_inline_bots",
60 | "url": "can_add_web_page_previews",
61 | "polls": "can_send_polls",
62 | "group_info": "can_change_info",
63 | "useradd": "can_invite_users",
64 | "pin": "can_pin_messages",
65 | }
66 |
67 |
68 | async def tg_lock(message, permissions: list, perm: str, lock: bool):
69 | if lock:
70 | if perm not in permissions:
71 | return await message.reply_text("Already locked.")
72 | permissions.remove(perm)
73 | elif perm in permissions:
74 | return await message.reply_text("Already Unlocked.")
75 | else:
76 | permissions.append(perm)
77 |
78 | permissions = {perm: True for perm in list(set(permissions))}
79 |
80 | try:
81 | await app.set_chat_permissions(
82 | message.chat.id, ChatPermissions(**permissions)
83 | )
84 | except ChatNotModified:
85 | return await message.reply_text(
86 | "To unlock this, you have to unlock 'messages' first."
87 | )
88 |
89 | await message.reply_text(("Locked." if lock else "Unlocked."))
90 |
91 |
92 | @app.on_message(filters.command(["lock", "unlock"]) & ~filters.private)
93 | @adminsOnly("can_restrict_members")
94 | async def locks_func(_, message):
95 | if len(message.command) != 2:
96 | return await message.reply_text(incorrect_parameters)
97 |
98 | chat_id = message.chat.id
99 | parameter = message.text.strip().split(None, 1)[1].lower()
100 | state = message.command[0].lower()
101 |
102 | if parameter not in data and parameter != "all":
103 | return await message.reply_text(incorrect_parameters)
104 |
105 | permissions = await current_chat_permissions(chat_id)
106 |
107 | if parameter in data:
108 | await tg_lock(
109 | message,
110 | permissions,
111 | data[parameter],
112 | bool(state == "lock"),
113 | )
114 | elif parameter == "all" and state == "lock":
115 | await app.set_chat_permissions(chat_id, ChatPermissions())
116 | await message.reply_text(f"Locked Everything in {message.chat.title}")
117 |
118 | elif parameter == "all" and state == "unlock":
119 | await app.set_chat_permissions(
120 | chat_id,
121 | ChatPermissions(
122 | can_send_messages=True,
123 | can_send_media_messages=True,
124 | can_send_stickers=True,
125 | can_send_animations=True,
126 | can_invite_users=True,
127 | can_send_games=True,
128 | can_use_inline_bots=True,
129 | can_send_polls=True,
130 | can_add_web_page_previews=True,
131 | ),
132 | )
133 | await message.reply(f"Unlocked Everything in {message.chat.title}")
134 |
135 |
136 | @app.on_message(filters.command("locks") & ~filters.private)
137 | @capture_err
138 | async def locktypes(_, message):
139 | permissions = await current_chat_permissions(message.chat.id)
140 |
141 | if not permissions:
142 | return await message.reply_text("No Permissions.")
143 |
144 | perms = "".join(f"__**{i}**__\n" for i in permissions)
145 | await message.reply_text(perms)
146 |
147 |
148 | @app.on_message(filters.text & ~filters.private, group=69)
149 | async def url_detector(_, message):
150 | user = message.from_user
151 | chat_id = message.chat.id
152 | text = message.text.lower().strip()
153 |
154 | if not text or not user:
155 | return
156 | if user.id in (SUDOERS + (await list_admins(chat_id))):
157 | return
158 |
159 | check = get_urls_from_text(text)
160 | if check:
161 | permissions = await current_chat_permissions(chat_id)
162 | if "can_add_web_page_previews" not in permissions:
163 | try:
164 | await message.delete()
165 | except Exception:
166 | await message.reply_text(
167 | "This message contains a URL, "
168 | + "but i don't have enough permissions to delete it"
169 | )
170 |
--------------------------------------------------------------------------------
/nezuko/modules/mongo_backup.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from os import remove
25 | from os import system as execute
26 |
27 | from pyrogram import filters
28 | from pyrogram.types import Message
29 |
30 | from nezuko import MONGO_URL, SUDOERS, app
31 |
32 |
33 | @app.on_message(
34 | filters.command("backup") & filters.user(SUDOERS) & filters.private
35 | )
36 | async def backup(_, message: Message):
37 | m = await message.reply("Backing up data...")
38 |
39 | code = execute(f'mongodump --uri "{MONGO_URL}"')
40 | if int(code) != 0:
41 | return await m.edit(
42 | "Looks like you don't have mongo-database-tools installed "
43 | + "grab it from mongodb.com/try/download/database-tools"
44 | )
45 |
46 | code = execute("zip backup.zip -r9 dump/*")
47 | if int(code) != 0:
48 | return await m.edit(
49 | "Looks like you don't have `zip` package installed, BACKUP FAILED!"
50 | )
51 |
52 | await message.reply_document("backup.zip")
53 | await m.delete()
54 | remove("backup.zip")
55 |
--------------------------------------------------------------------------------
/nezuko/modules/music.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from __future__ import unicode_literals
25 |
26 | import os
27 | from asyncio import get_running_loop
28 | from functools import partial
29 | from io import BytesIO
30 | from urllib.parse import urlparse
31 |
32 | import ffmpeg
33 | import youtube_dl
34 | from pyrogram import filters
35 |
36 | from nezuko import aiohttpsession as session
37 | from nezuko import app, arq
38 | from nezuko.core.decorators.errors import capture_err
39 | from nezuko.utils.pastebin import paste
40 |
41 | __MODULE__ = "Music"
42 | __HELP__ = """
43 | /ytmusic [link] To Download Music From Various Websites Including Youtube. [SUDOERS]
44 | /saavn [query] To Download Music From Saavn.
45 | /lyrics [query] To Get Lyrics Of A Song.
46 | """
47 |
48 | is_downloading = False
49 |
50 |
51 | def get_file_extension_from_url(url):
52 | url_path = urlparse(url).path
53 | basename = os.path.basename(url_path)
54 | return basename.split(".")[-1]
55 |
56 |
57 | def download_youtube_audio(url: str):
58 | global is_downloading
59 | with youtube_dl.YoutubeDL(
60 | {
61 | "format": "bestaudio",
62 | "writethumbnail": True,
63 | "quiet": True,
64 | }
65 | ) as ydl:
66 | info_dict = ydl.extract_info(url, download=False)
67 | if int(float(info_dict["duration"])) > 600:
68 | is_downloading = False
69 | return []
70 | ydl.process_info(info_dict)
71 | audio_file = ydl.prepare_filename(info_dict)
72 | basename = audio_file.rsplit(".", 1)[-2]
73 | if info_dict["ext"] == "webm":
74 | audio_file_opus = basename + ".opus"
75 | ffmpeg.input(audio_file).output(
76 | audio_file_opus, codec="copy", loglevel="error"
77 | ).overwrite_output().run()
78 | os.remove(audio_file)
79 | audio_file = audio_file_opus
80 | thumbnail_url = info_dict["thumbnail"]
81 | thumbnail_file = (
82 | basename + "." + get_file_extension_from_url(thumbnail_url)
83 | )
84 | title = info_dict["title"]
85 | try:
86 | performer = info_dict["artist"]
87 | except:
88 | performer = info_dict["uploader"]
89 | duration = int(float(info_dict["duration"]))
90 | return [title, performer, duration, audio_file, thumbnail_file]
91 |
92 |
93 | @app.on_message(filters.command("ytmusic"))
94 | @capture_err
95 | async def music(_, message):
96 | global is_downloading
97 | if len(message.command) != 2:
98 | return await message.reply_text("/ytmusic needs a link as argument")
99 | url = message.text.split(None, 1)[1]
100 | if is_downloading:
101 | return await message.reply_text(
102 | "Another download is in progress, try again after sometime."
103 | )
104 | is_downloading = True
105 | m = await message.reply_text(
106 | f"Downloading {url}", disable_web_page_preview=True
107 | )
108 | try:
109 | loop = get_running_loop()
110 | music = await loop.run_in_executor(
111 | None, partial(download_youtube_audio, url)
112 | )
113 | if not music:
114 | await m.edit("Too Long, Can't Download.")
115 | (
116 | title,
117 | performer,
118 | duration,
119 | audio_file,
120 | thumbnail_file,
121 | ) = music
122 | except Exception as e:
123 | is_downloading = False
124 | return await m.edit(str(e))
125 | await message.reply_audio(
126 | audio_file,
127 | duration=duration,
128 | performer=performer,
129 | title=title,
130 | thumb=thumbnail_file,
131 | )
132 | await m.delete()
133 | os.remove(audio_file)
134 | os.remove(thumbnail_file)
135 | is_downloading = False
136 |
137 |
138 | # Funtion To Download Song
139 | async def download_song(url):
140 | async with session.get(url) as resp:
141 | song = await resp.read()
142 | song = BytesIO(song)
143 | song.name = "a.mp3"
144 | return song
145 |
146 |
147 | # Jiosaavn Music
148 |
149 |
150 | @app.on_message(filters.command("saavn"))
151 | @capture_err
152 | async def jssong(_, message):
153 | global is_downloading
154 | if len(message.command) < 2:
155 | return await message.reply_text("/saavn requires an argument.")
156 | if is_downloading:
157 | return await message.reply_text(
158 | "Another download is in progress, try again after sometime."
159 | )
160 | is_downloading = True
161 | text = message.text.split(None, 1)[1]
162 | m = await message.reply_text("Searching...")
163 | try:
164 | songs = await arq.saavn(text)
165 | if not songs.ok:
166 | await m.edit(songs.result)
167 | is_downloading = False
168 | return
169 | sname = songs.result[0].song
170 | slink = songs.result[0].media_url
171 | ssingers = songs.result[0].singers
172 | sduration = songs.result[0].duration
173 | await m.edit("Downloading")
174 | song = await download_song(slink)
175 | await m.edit("Uploading")
176 | await message.reply_audio(
177 | audio=song,
178 | title=sname,
179 | performer=ssingers,
180 | duration=sduration,
181 | )
182 | await m.delete()
183 | except Exception as e:
184 | is_downloading = False
185 | return await m.edit(str(e))
186 | is_downloading = False
187 | song.close()
188 |
189 |
190 | # Lyrics
191 |
192 |
193 | @app.on_message(filters.command("lyrics"))
194 | async def lyrics_func(_, message):
195 | if len(message.command) < 2:
196 | return await message.reply_text("**Usage:**\n/lyrics [QUERY]")
197 | m = await message.reply_text("**Searching**")
198 | query = message.text.strip().split(None, 1)[1]
199 | song = await arq.lyrics(query)
200 | lyrics = song.result
201 | if len(lyrics) < 4095:
202 | return await m.edit(f"__{lyrics}__")
203 | lyrics = await paste(lyrics)
204 | await m.edit(f"**LYRICS_TOO_LONG:** [URL]({lyrics})")
205 |
--------------------------------------------------------------------------------
/nezuko/modules/notes.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from re import findall
25 |
26 | from pyrogram import filters
27 |
28 | from nezuko import LOG_GROUP_ID as USERBOT_ID
29 | from nezuko import SUDOERS, app, eor
30 | from nezuko.core.decorators.errors import capture_err
31 | from nezuko.core.decorators.permissions import adminsOnly
32 | from nezuko.core.keyboard import ikb
33 | from nezuko.utils.dbfunctions import (
34 | delete_note,
35 | get_note,
36 | get_note_names,
37 | save_note,
38 | )
39 | from nezuko.utils.functions import extract_text_and_keyb
40 |
41 | __MODULE__ = "Notes"
42 | __HELP__ = """/notes To Get All The Notes In The Chat.
43 |
44 | /save [NOTE_NAME] To Save A Note (Can be a sticker or text).
45 |
46 | #NOTE_NAME To Get A Note.
47 |
48 | /delete [NOTE_NAME] To Delete A Note.
49 |
50 | Checkout /markdownhelp to know more about formattings and other syntax.
51 | """
52 |
53 | USERBOT_PREFIX = "."
54 |
55 |
56 | @app.on_message(filters.command("save") & ~filters.private)
57 | @adminsOnly("can_change_info")
58 | async def save_notee(_, message):
59 | if len(message.command) < 2 or not message.reply_to_message:
60 | await eor(
61 | message,
62 | text="**Usage:**\nReply to a text or sticker with /save [NOTE_NAME] to save it.",
63 | )
64 |
65 | elif (
66 | not message.reply_to_message.text
67 | and not message.reply_to_message.sticker
68 | ):
69 | await eor(
70 | message,
71 | text="__**You can only save text or stickers in notes.**__",
72 | )
73 | else:
74 | name = message.text.split(None, 1)[1].strip()
75 | if not name:
76 | return await eor(message, text="**Usage**\n__/save [NOTE_NAME]__")
77 | _type = "text" if message.reply_to_message.text else "sticker"
78 | note = {
79 | "type": _type,
80 | "data": message.reply_to_message.text.markdown
81 | if _type == "text"
82 | else message.reply_to_message.sticker.file_id,
83 | }
84 | prefix = message.text.split()[0][0]
85 | chat_id = message.chat.id if prefix != USERBOT_PREFIX else USERBOT_ID
86 | await save_note(chat_id, name, note)
87 | await eor(message, text=f"__**Saved note {name}.**__")
88 |
89 |
90 | @app.on_message(filters.command("notes") & ~filters.private)
91 | @capture_err
92 | async def get_notes(_, message):
93 | prefix = message.text.split()[0][0]
94 | is_ubot = bool(prefix == USERBOT_PREFIX)
95 | chat_id = USERBOT_ID if is_ubot else message.chat.id
96 |
97 | _notes = await get_note_names(chat_id)
98 |
99 | if not _notes:
100 | return await eor(message, text="**No notes in this chat.**")
101 | _notes.sort()
102 | msg = f"List of notes in {'USERBOT' if is_ubot else message.chat.title}\n"
103 | for note in _notes:
104 | msg += f"**-** `{note}`\n"
105 | await eor(message, text=msg)
106 |
107 |
108 | async def get_one_note_userbot(_, message):
109 | if len(message.text.split()) < 2:
110 | return await eor(message, text="Invalid arguments")
111 |
112 | name = message.text.split(None, 1)[1]
113 |
114 | _note = await get_note(USERBOT_ID, name)
115 | if not _note:
116 | return await eor(message, text="No such note.")
117 |
118 | if _note["type"] == "text":
119 | data = _note["data"]
120 | await eor(
121 | message,
122 | text=data,
123 | disable_web_page_preview=True,
124 | )
125 | else:
126 | await message.reply_sticker(_note["data"])
127 |
128 |
129 | @app.on_message(filters.regex(r"^#.+") & filters.text & ~filters.private)
130 | @capture_err
131 | async def get_one_note(_, message):
132 | name = message.text.replace("#", "", 1)
133 | if not name:
134 | return
135 | _note = await get_note(message.chat.id, name)
136 | if not _note:
137 | return
138 | if _note["type"] == "text":
139 | data = _note["data"]
140 | keyb = None
141 | if findall(r"\[.+\,.+\]", data):
142 | keyboard = extract_text_and_keyb(ikb, data)
143 | if keyboard:
144 | data, keyb = keyboard
145 | await message.reply_text(
146 | data,
147 | reply_markup=keyb,
148 | disable_web_page_preview=True,
149 | )
150 | else:
151 | await message.reply_sticker(_note["data"])
152 |
153 |
154 | @app.on_message(filters.command("delete") & ~filters.private)
155 | @adminsOnly("can_change_info")
156 | async def del_note(_, message):
157 | if len(message.command) < 2:
158 | return await eor(message, text="**Usage**\n__/delete [NOTE_NAME]__")
159 | name = message.text.split(None, 1)[1].strip()
160 | if not name:
161 | return await eor(message, text="**Usage**\n__/delete [NOTE_NAME]__")
162 |
163 | prefix = message.text.split()[0][0]
164 | is_ubot = bool(prefix == USERBOT_PREFIX)
165 | chat_id = USERBOT_ID if is_ubot else message.chat.id
166 |
167 | deleted = await delete_note(chat_id, name)
168 | if deleted:
169 | await eor(message, text=f"**Deleted note {name} successfully.**")
170 | else:
171 | await eor(message, text="**No such note.**")
172 |
--------------------------------------------------------------------------------
/nezuko/modules/parse_preview.py:
--------------------------------------------------------------------------------
1 | from asyncio import sleep
2 |
3 | from pyrogram import filters
4 | from pyrogram.types import Message
5 |
6 | from nezuko import SUDOERS, app, eor
7 | from nezuko.core.sections import section
8 |
9 |
10 | @app.on_message(
11 | filters.command("parse_preview") & filters.user(SUDOERS),
12 | )
13 | async def parse(_, message: Message):
14 | r = message.reply_to_message
15 | has_wpp = False
16 |
17 | m_ = await eor(message, text="Parsing...")
18 | if not r:
19 | return await m_.edit("Reply to a message with a webpage")
20 |
21 | if not r.web_page:
22 | text = r.text or r.caption
23 | if text:
24 | m = await app.send_message(m_.chat.id, text)
25 | await sleep(1)
26 | await m.delete()
27 | if m.web_page:
28 | r = m
29 | has_wpp = True
30 | else:
31 | has_wpp = True
32 |
33 | if not has_wpp:
34 | return await m_.edit(
35 | "Replied message has no webpage preview.",
36 | )
37 |
38 | wpp = r.web_page
39 |
40 | body = {
41 | "Title": [wpp.title or "Null"],
42 | "Description": [
43 | (wpp.description[:50] + "...") if wpp.description else "Null"
44 | ],
45 | "URL": [wpp.display_url or "Null"],
46 | "Author": [wpp.author or "Null"],
47 | "Site Name": [wpp.site_name or "Null"],
48 | "Type": wpp.type or "Null",
49 | }
50 |
51 | text = section("Preview", body)
52 |
53 | t = wpp.type
54 |
55 | if t == "photo":
56 | media = wpp.photo
57 | func = app.send_photo
58 | elif t == "audio":
59 | media = wpp.audio
60 | func = app.send_audio
61 | elif t == "video":
62 | media = wpp.video
63 | func = app.send_video
64 | elif t == "document":
65 | media = wpp.document
66 | func = app.send_document
67 | else:
68 | media = None
69 | func = None
70 |
71 | if media and func:
72 | await m_.delete()
73 | return await func(
74 | m_.chat.id,
75 | media.file_id,
76 | caption=text,
77 | )
78 |
79 | await m_.edit(text, disable_web_page_preview=True)
80 |
--------------------------------------------------------------------------------
/nezuko/modules/paste.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | import os
25 | import re
26 |
27 | import aiofiles
28 | from pyrogram import filters
29 | from pyrogram.types import Message
30 |
31 | from nezuko import SUDOERS, app, eor
32 | from nezuko.core.decorators.errors import capture_err
33 | from nezuko.core.keyboard import ikb
34 | from nezuko.utils.pastebin import paste
35 |
36 | __MODULE__ = "Paste"
37 | __HELP__ = "/paste - To Paste Replied Text Or Document To A Pastebin"
38 | pattern = re.compile(r"^text/|json$|yaml$|xml$|toml$|x-sh$|x-shellscript$")
39 |
40 |
41 | @app.on_message(filters.command("paste"))
42 | @capture_err
43 | async def paste_func(_, message: Message):
44 | if not message.reply_to_message:
45 | return await eor(message, text="Reply To A Message With /paste")
46 | r = message.reply_to_message
47 |
48 | if not r.text and not r.document:
49 | return await eor(
50 | message, text="Only text and documents are supported."
51 | )
52 |
53 | m = await eor(message, text="Pasting...")
54 |
55 | if r.text:
56 | content = str(r.text)
57 | elif r.document:
58 | if r.document.file_size > 40000:
59 | return await m.edit("You can only paste files smaller than 40KB.")
60 |
61 | if not pattern.search(r.document.mime_type):
62 | return await m.edit("Only text files can be pasted.")
63 |
64 | doc = await message.reply_to_message.download()
65 |
66 | async with aiofiles.open(doc, mode="r") as f:
67 | content = await f.read()
68 |
69 | os.remove(doc)
70 |
71 | link = await paste(content)
72 | kb = ikb({"Paste Link": link})
73 | try:
74 | if m.from_user.is_bot:
75 | await message.reply_photo(
76 | photo=link,
77 | quote=False,
78 | reply_markup=kb,
79 | )
80 | else:
81 | await message.reply_photo(
82 | photo=link,
83 | quote=False,
84 | caption=f"**Paste Link:** [Here]({link})",
85 | )
86 | await m.delete()
87 | except Exception:
88 | await m.edit("Here's your paste", reply_markup=kb)
89 |
--------------------------------------------------------------------------------
/nezuko/modules/pipes.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | import asyncio
25 |
26 | from pyrogram import filters
27 | from pyrogram.types import Message
28 |
29 | from nezuko import BOT_ID, SUDOERS, app
30 | from nezuko.core.decorators.errors import capture_err
31 |
32 | __MODULE__ = "Pipes"
33 | __HELP__ = """
34 | **THIS MODULE IS ONLY FOR DEVS**
35 |
36 | Use this module to create a pipe that will forward messages of one chat/channel to another.
37 |
38 |
39 | /activate_pipe [FROM_CHAT_ID] [TO_CHAT_ID] [BOT|USERBOT]
40 |
41 | Active a pipe.
42 |
43 | choose 'BOT' or 'USERBOT' according to your needs,
44 | this will decide which client will fetch the
45 | message from 'FROM_CHAT'.
46 |
47 |
48 | /deactivate_pipe [FROM_CHAT_ID]
49 | Deactivete a pipe.
50 |
51 |
52 | /show_pipes
53 | Show all the active pipes.
54 |
55 | **NOTE:**
56 | These pipes are only temporary, and will be destroyed
57 | on restart.
58 | """
59 | pipes_list_bot = {}
60 | pipes_list_userbot = {}
61 |
62 |
63 | @app.on_message(~filters.me, group=500)
64 | @capture_err
65 | async def pipes_worker_bot(_, message: Message):
66 | chat_id = message.chat.id
67 | if chat_id in pipes_list_bot:
68 | await message.forward(pipes_list_bot[chat_id])
69 |
70 |
71 | @app.on_message(filters.command("activate_pipe") & filters.user(SUDOERS))
72 | @capture_err
73 | async def activate_pipe_func(_, message: Message):
74 | global pipes_list_bot, pipes_list_userbot
75 |
76 | if len(message.command) != 4:
77 | return await message.reply(
78 | "**Usage:**\n/activate_pipe [FROM_CHAT_ID] [TO_CHAT_ID] [BOT|USERBOT]"
79 | )
80 |
81 | text = message.text.strip().split()
82 |
83 | from_chat = int(text[1])
84 | to_chat = int(text[2])
85 | fetcher = text[3].lower()
86 |
87 | if fetcher not in ["bot", "userbot"]:
88 | return await message.reply("Wrong fetcher, see help menu.")
89 |
90 | if from_chat in pipes_list_bot or from_chat in pipes_list_userbot:
91 | return await message.reply_text("This pipe is already active.")
92 |
93 | dict_ = pipes_list_bot
94 | if fetcher == "userbot":
95 | dict_ = pipes_list_userbot
96 |
97 | dict_[from_chat] = to_chat
98 | await message.reply_text("Activated pipe.")
99 |
100 |
101 | @app.on_message(filters.command("deactivate_pipe") & filters.user(SUDOERS))
102 | @capture_err
103 | async def deactivate_pipe_func(_, message: Message):
104 | global pipes_list_bot, pipes_list_userbot
105 |
106 | if len(message.command) != 2:
107 | await message.reply_text("**Usage:**\n/deactivate_pipe [FROM_CHAT_ID]")
108 | return
109 | text = message.text.strip().split()
110 | from_chat = int(text[1])
111 |
112 | if from_chat not in pipes_list_bot and from_chat not in pipes_list_userbot:
113 | await message.reply_text("This pipe is already inactive.")
114 |
115 | dict_ = pipes_list_bot
116 | if from_chat in pipes_list_userbot:
117 | dict_ = pipes_list_userbot
118 |
119 | del dict_[from_chat]
120 | await message.reply_text("Deactivated pipe.")
121 |
122 |
123 | @app.on_message(filters.command("pipes") & filters.user(SUDOERS))
124 | @capture_err
125 | async def show_pipes_func(_, message: Message):
126 | pipes_list_bot.update(pipes_list_userbot)
127 | if not pipes_list_bot:
128 | return await message.reply_text("No pipe is active.")
129 |
130 | text = "".join(
131 | (
132 | f"**Pipe:** `{count}`\n**From:** `{pipe[0]}`\n"
133 | + f"**To:** `{pipe[1]}`\n\n"
134 | )
135 | for count, pipe in enumerate(pipes_list_bot.items(), 1)
136 | )
137 | await message.reply_text(text)
138 |
--------------------------------------------------------------------------------
/nezuko/modules/proxy.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 |
25 | from asyncio import get_event_loop, sleep
26 |
27 | from pyrogram import filters
28 | from pyrogram.types import CallbackQuery, Message
29 |
30 | from nezuko import app, arq
31 | from nezuko.core.keyboard import ikb
32 |
33 | __MODULE__ = "Proxy"
34 | __HELP__ = (
35 | "/proxy - Get socks5 proxy which you can"
36 | + " use with telegram or other things"
37 | )
38 |
39 | proxies = []
40 |
41 |
42 | async def get_proxies():
43 | global proxies
44 | proxies = (await arq.proxy()).result
45 |
46 |
47 | loop = get_event_loop()
48 | loop.create_task(get_proxies())
49 |
50 |
51 | def url_from_proxy(proxy: str) -> str:
52 | creds, proxy = proxy.split("//")[1:][0].split("@")
53 | user, passwd = creds.split(":")
54 | host, port = proxy.split(":")
55 | return (
56 | f"https://t.me/socks?server={host}&port="
57 | + f"{port}&user={user}&pass={passwd}"
58 | )
59 |
60 |
61 | @app.on_message(filters.command("proxy"))
62 | async def proxy_func(_, message: Message):
63 | if len(proxies) == 0:
64 | await sleep(0.5)
65 | location = proxies[0].location
66 | proxy = proxies[0].proxy
67 | url = url_from_proxy(proxy)
68 | keyb = ikb(
69 | {
70 | "←": "proxy_arq_-1",
71 | "→": "proxy_arq_1",
72 | "Connect": url,
73 | }
74 | )
75 | await message.reply_text(
76 | f"""
77 | **Proxy:** {proxy}
78 | **Location**: {location}
79 |
80 | **POWERED BY [ARQ](http://t.me/ARQUpdates)**""",
81 | reply_markup=keyb,
82 | disable_web_page_preview=True,
83 | )
84 |
85 |
86 | @app.on_callback_query(filters.regex(r"proxy_arq_"))
87 | async def proxy_callback_func(_, cq: CallbackQuery):
88 | data = cq.data
89 | index = int(data.split("_")[-1])
90 | location = proxies[index].location
91 | proxy = proxies[index].proxy
92 | url = url_from_proxy(proxy)
93 | keyb = ikb(
94 | {
95 | "←": f"proxy_arq_{index-1}",
96 | "→": f"proxy_arq_{index+1}",
97 | "Connect": url,
98 | }
99 | )
100 | await cq.message.edit(
101 | f"""
102 | **Proxy:** {proxy}
103 | **Location**: {location}
104 |
105 | **POWERED BY [ARQ](http://t.me/ARQUpdates)**""",
106 | reply_markup=keyb,
107 | disable_web_page_preview=True,
108 | )
109 |
--------------------------------------------------------------------------------
/nezuko/modules/quotly.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from io import BytesIO
25 | from traceback import format_exc
26 |
27 | from pyrogram import filters
28 | from pyrogram.types import Message
29 |
30 | from nezuko import SUDOERS, app, arq
31 | from nezuko.core.decorators.errors import capture_err
32 |
33 | __MODULE__ = "Quotly"
34 | __HELP__ = """
35 | /q - To quote a message.
36 | /q [INTEGER] - To quote more than 1 messages.
37 | /q r - to quote a message with it's reply
38 |
39 | Use .q to quote using userbot
40 | """
41 |
42 |
43 | async def quotify(messages: list):
44 | response = await arq.quotly(messages)
45 | if not response.ok:
46 | return [False, response.result]
47 | sticker = response.result
48 | sticker = BytesIO(sticker)
49 | sticker.name = "sticker.webp"
50 | return [True, sticker]
51 |
52 |
53 | def getArg(message: Message) -> str:
54 | return message.text.strip().split(None, 1)[1].strip()
55 |
56 |
57 | def isArgInt(message: Message) -> list:
58 | count = getArg(message)
59 | try:
60 | count = int(count)
61 | return [True, count]
62 | except ValueError:
63 | return [False, 0]
64 |
65 |
66 | @app.on_message(filters.command("q") & ~filters.private)
67 | @capture_err
68 | async def quotly_func(client, message: Message):
69 | if not message.reply_to_message:
70 | return await message.reply_text("Reply to a message to quote it.")
71 | if not message.reply_to_message.text:
72 | return await message.reply_text(
73 | "Replied message has no text, can't quote it."
74 | )
75 | m = await message.reply_text("Quoting Messages")
76 | if len(message.command) < 2:
77 | messages = [message.reply_to_message]
78 |
79 | elif len(message.command) == 2:
80 | arg = isArgInt(message)
81 | if arg[0]:
82 | if arg[1] < 2 or arg[1] > 10:
83 | return await m.edit("Argument must be between 2-10.")
84 |
85 | count = arg[1]
86 |
87 | # Fetching 5 extra messages so tha twe can ignore media
88 | # messages and still end up with correct offset
89 | messages = [
90 | i
91 | for i in await client.get_messages(
92 | message.chat.id,
93 | range(
94 | message.reply_to_message.id,
95 | message.reply_to_message.id + (count + 5),
96 | ),
97 | replies=0,
98 | )
99 | if not i.empty and not i.media
100 | ]
101 | messages = messages[:count]
102 | else:
103 | if getArg(message) != "r":
104 | return await m.edit(
105 | "Incorrect Argument, Pass **'r'** or **'INT'**, **EX:** __/q 2__"
106 | )
107 | reply_message = await client.get_messages(
108 | message.chat.id,
109 | message.reply_to_message.id,
110 | replies=1,
111 | )
112 | messages = [reply_message]
113 | else:
114 | return await m.edit(
115 | "Incorrect argument, check quotly module in help section."
116 | )
117 | try:
118 | if not message:
119 | return await m.edit("Something went wrong.")
120 |
121 | sticker = await quotify(messages)
122 | if not sticker[0]:
123 | await message.reply_text(sticker[1])
124 | return await m.delete()
125 | sticker = sticker[1]
126 | await message.reply_sticker(sticker)
127 | await m.delete()
128 | sticker.close()
129 | except Exception as e:
130 | await m.edit(
131 | "Something went wrong while quoting messages,"
132 | + " This error usually happens when there's a "
133 | + " message containing something other than text,"
134 | + " or one of the messages in-between are deleted."
135 | )
136 | e = format_exc()
137 | print(e)
138 |
--------------------------------------------------------------------------------
/nezuko/modules/reddit.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from pyrogram import filters
25 |
26 | from nezuko import app, arq
27 | from nezuko.core.decorators.errors import capture_err
28 | from nezuko.utils.dbfunctions import get_nsfw_status
29 |
30 | __MODULE__ = "Reddit"
31 | __HELP__ = "/reddit [query] - results something from reddit"
32 |
33 |
34 | @app.on_message(filters.command("reddit"))
35 | @capture_err
36 | async def reddit(_, message):
37 | if len(message.command) != 2:
38 | return await message.reply_text("/reddit needs an argument")
39 | subreddit = message.text.split(None, 1)[1]
40 | m = await message.reply_text("Searching")
41 | reddit = await arq.reddit(subreddit)
42 | if not reddit.ok:
43 | return await m.edit(reddit.result)
44 | reddit = reddit.result
45 | nsfw = reddit.nsfw
46 | sreddit = reddit.subreddit
47 | title = reddit.title
48 | image = reddit.url
49 | link = reddit.postLink
50 | if nsfw:
51 | if await get_nsfw_status(message.chat.id) == False:
52 | return await m.edit(
53 | "NSFW content is disabled in this chat! Enable it using /nsfw"
54 | )
55 | caption = f"""
56 | **Title:** `{title}`
57 | **Subreddit:** {sreddit}
58 | **PostLink:** {link}"""
59 | try:
60 | await message.reply_photo(photo=image, caption=caption)
61 | await m.delete()
62 | except Exception as e:
63 | await m.edit(e.MESSAGE)
64 |
--------------------------------------------------------------------------------
/nezuko/modules/regex.py:
--------------------------------------------------------------------------------
1 | # https://github.com/PaulSonOfLars/tgbot/blob/master/tg_bot/modules/sed.py
2 | import re
3 | import sre_constants
4 |
5 | from pyrogram import filters
6 |
7 | from nezuko import app
8 | from nezuko.utils.filter_groups import regex_group
9 |
10 | __MODULE__ = "Sed"
11 | __HELP__ = "**Usage:**\ns/foo/bar"
12 |
13 |
14 | DELIMITERS = ("/", ":", "|", "_")
15 |
16 |
17 | @app.on_message(
18 | filters.regex(r"s([{}]).*?\1.*".format("".join(DELIMITERS))),
19 | group=regex_group,
20 | )
21 | async def sed(_, message):
22 | if not message.text:
23 | return
24 | sed_result = separate_sed(message.text)
25 | if message.reply_to_message:
26 | if message.reply_to_message.text:
27 | to_fix = message.reply_to_message.text
28 | elif message.reply_to_message.caption:
29 | to_fix = message.reply_to_message.caption
30 | else:
31 | return
32 | try:
33 | repl, repl_with, flags = sed_result
34 | except Exception:
35 | return
36 |
37 | if not repl:
38 | return await message.reply_text(
39 | "You're trying to replace... " "nothing with something?"
40 | )
41 |
42 | try:
43 |
44 | if infinite_checker(repl):
45 | return await message.reply_text("Nice try -_-")
46 |
47 | if "i" in flags and "g" in flags:
48 | text = re.sub(repl, repl_with, to_fix, flags=re.I).strip()
49 | elif "i" in flags:
50 | text = re.sub(
51 | repl, repl_with, to_fix, count=1, flags=re.I
52 | ).strip()
53 | elif "g" in flags:
54 | text = re.sub(repl, repl_with, to_fix).strip()
55 | else:
56 | text = re.sub(repl, repl_with, to_fix, count=1).strip()
57 | except sre_constants.error:
58 | return
59 |
60 | # empty string errors -_-
61 | if len(text) >= 4096:
62 | await message.reply_text(
63 | "The result of the sed command was too long for \
64 | telegram!"
65 | )
66 | elif text:
67 | await message.reply_to_message.reply_text(text)
68 |
69 |
70 | def infinite_checker(repl):
71 | regex = [
72 | r"\((.{1,}[\+\*]){1,}\)[\+\*].",
73 | r"[\(\[].{1,}\{\d(,)?\}[\)\]]\{\d(,)?\}",
74 | r"\(.{1,}\)\{.{1,}(,)?\}\(.*\)(\+|\* |\{.*\})",
75 | ]
76 | for match in regex:
77 | status = re.search(match, repl)
78 | return bool(status)
79 |
80 |
81 | def separate_sed(sed_string):
82 | if (
83 | len(sed_string) < 3
84 | or sed_string[1] not in DELIMITERS
85 | or sed_string.count(sed_string[1]) < 2
86 | ):
87 | return
88 |
89 | delim = sed_string[1]
90 | start = counter = 2
91 | while counter < len(sed_string):
92 | if sed_string[counter] == "\\":
93 | counter += 1
94 |
95 | elif sed_string[counter] == delim:
96 | replace = sed_string[start:counter]
97 | counter += 1
98 | start = counter
99 | break
100 |
101 | counter += 1
102 |
103 | else:
104 | return None
105 | while counter < len(sed_string):
106 | if (
107 | sed_string[counter] == "\\"
108 | and counter + 1 < len(sed_string)
109 | and sed_string[counter + 1] == delim
110 | ):
111 | sed_string = sed_string[:counter] + sed_string[counter + 1 :]
112 |
113 | elif sed_string[counter] == delim:
114 | replace_with = sed_string[start:counter]
115 | counter += 1
116 | break
117 |
118 | counter += 1
119 | else:
120 | return replace, sed_string[start:], ""
121 |
122 | flags = sed_string[counter:] if counter < len(sed_string) else ""
123 | return replace, replace_with, flags.lower()
124 |
--------------------------------------------------------------------------------
/nezuko/modules/repo.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from pyrogram import filters
25 |
26 | from nezuko import app
27 | from nezuko.core.decorators.errors import capture_err
28 | from nezuko.utils.http import get
29 |
30 | __MODULE__ = "Repo"
31 | __HELP__ = "/repo - To Get My Github Repository Link " "And Support Group Link"
32 |
33 |
34 | @app.on_message(filters.command("repo"))
35 | @capture_err
36 | async def repo(_, message):
37 | users = await get(
38 | "https://api.github.com/repos/rozari0/NezukoBot/contributors"
39 | )
40 | list_of_users = "".join(
41 | f"**{count}.** [{user['login']}]({user['html_url']})\n"
42 | for count, user in enumerate(users, start=1)
43 | )
44 |
45 | text = f"""[Github](https://github.com/rozari0/NezukoBot) | [Group](t.me/thecrowclub)
46 | ```----------------
47 | | Contributors |
48 | ----------------```
49 | {list_of_users}"""
50 | await app.send_message(
51 | message.chat.id, text=text, disable_web_page_preview=True
52 | )
53 |
--------------------------------------------------------------------------------
/nezuko/modules/reverse.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | import os
25 | from asyncio import gather, get_running_loop
26 | from base64 import b64decode
27 | from io import BytesIO
28 | from random import randint
29 |
30 | import aiofiles
31 | import requests
32 | from bs4 import BeautifulSoup
33 | from pyrogram import filters
34 | from pyrogram.types import InputMediaPhoto, Message
35 |
36 | from nezuko import MESSAGE_DUMP_CHAT, SUDOERS, app, eor
37 | from nezuko.core.decorators.errors import capture_err
38 | from nezuko.utils.functions import get_file_id_from_message
39 | from nezuko.utils.http import get
40 |
41 |
42 | async def get_soup(url: str, headers):
43 | html = await get(url, headers=headers)
44 | return BeautifulSoup(html, "html.parser")
45 |
46 |
47 | @app.on_message(filters.command("reverse"))
48 | @capture_err
49 | async def reverse_image_search(client, message: Message):
50 | if not message.reply_to_message:
51 | return await eor(
52 | message, text="Reply to a message to reverse search it."
53 | )
54 | reply = message.reply_to_message
55 | if (
56 | not reply.document
57 | and not reply.photo
58 | and not reply.sticker
59 | and not reply.animation
60 | and not reply.video
61 | ):
62 | return await eor(
63 | message,
64 | text="Reply to an image/document/sticker/animation to reverse search it.",
65 | )
66 | m = await eor(message, text="Searching...")
67 | file_id = get_file_id_from_message(reply)
68 | if not file_id:
69 | return await m.edit("Can't reverse that")
70 | image = await client.download_media(file_id, f"{randint(1000, 10000)}.jpg")
71 | async with aiofiles.open(image, "rb") as f:
72 | if image:
73 | search_url = "http://www.google.com/searchbyimage/upload"
74 | multipart = {
75 | "encoded_image": (image, await f.read()),
76 | "image_content": "",
77 | }
78 |
79 | def post_non_blocking():
80 | return requests.post(
81 | search_url, files=multipart, allow_redirects=False
82 | )
83 |
84 | loop = get_running_loop()
85 | response = await loop.run_in_executor(None, post_non_blocking)
86 | location = response.headers.get("Location")
87 | os.remove(image)
88 | else:
89 | return await m.edit("Something wrong happened.")
90 | headers = {
91 | "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0"
92 | }
93 |
94 | try:
95 | soup = await get_soup(location, headers=headers)
96 | div = soup.find_all("div", {"class": "r5a77d"})[0]
97 | text = div.find("a").text
98 | text = f"**Result**: [{text}]({location})"
99 | except Exception:
100 | return await m.edit(
101 | f"**Result**: [Link]({location})",
102 | disable_web_page_preview=True,
103 | )
104 |
105 | # Pass if no images detected
106 | try:
107 | url = "https://google.com" + soup.find_all(
108 | "a", {"class": "ekf0x hSQtef"}
109 | )[0].get("href")
110 |
111 | soup = await get_soup(url, headers=headers)
112 |
113 | media = []
114 | for img in soup.find_all("img"):
115 | if len(media) == 2:
116 | break
117 |
118 | if img.get("src"):
119 | img = img.get("src")
120 | if "image/gif" in img:
121 | continue
122 |
123 | img = BytesIO(b64decode(img))
124 | img.name = "img.png"
125 | media.append(img)
126 | elif img.get("data-src"):
127 | img = img.get("data-src")
128 | media.append(img)
129 |
130 | # Cache images, so we can use file_ids
131 | tasks = [client.send_photo(MESSAGE_DUMP_CHAT, img) for img in media]
132 | messages = await gather(*tasks)
133 |
134 | await message.reply_media_group(
135 | [
136 | InputMediaPhoto(
137 | i.photo.file_id,
138 | caption=text,
139 | )
140 | for i in messages
141 | ]
142 | )
143 | except Exception:
144 | pass
145 |
146 | await m.edit(
147 | text,
148 | disable_web_page_preview=True,
149 | )
150 |
--------------------------------------------------------------------------------
/nezuko/modules/rss.py:
--------------------------------------------------------------------------------
1 | from asyncio import get_event_loop, sleep
2 |
3 | from feedparser import parse
4 | from pyrogram import filters
5 | from pyrogram.types import Message
6 |
7 | from nezuko import RSS_DELAY, app
8 | from nezuko.core.decorators.errors import capture_err
9 | from nezuko.utils.dbfunctions import (
10 | add_rss_feed,
11 | get_rss_feeds,
12 | is_rss_active,
13 | remove_rss_feed,
14 | update_rss_feed,
15 | )
16 | from nezuko.utils.functions import get_http_status_code, get_urls_from_text
17 | from nezuko.utils.rss import Feed
18 |
19 | __MODULE__ = "RSS"
20 | __HELP__ = f"""
21 | /add_feed [URL] - Add a feed to chat
22 | /rm_feed - Remove feed from chat
23 |
24 | **Note:**
25 | - This will check for updates every {RSS_DELAY//60} minutes.
26 | - You can only add one feed per chat.
27 | - Currently RSS and ATOM feeds are supported.
28 | """
29 |
30 |
31 | async def rss_worker():
32 | print("[INFO]: RSS WORKER STARTED")
33 | while True:
34 | feeds = await get_rss_feeds()
35 | if not feeds:
36 | await sleep(RSS_DELAY)
37 | continue
38 |
39 | loop = get_event_loop()
40 |
41 | for _feed in feeds:
42 | try:
43 | chat = _feed["chat_id"]
44 | url = _feed["url"]
45 | last_title = _feed.get("last_title")
46 |
47 | parsed = await loop.run_in_executor(None, parse, url)
48 | feed = Feed(parsed)
49 |
50 | if feed.title == last_title:
51 | continue
52 |
53 | await app.send_message(
54 | chat, feed.parsed(), disable_web_page_preview=True
55 | )
56 | await update_rss_feed(chat, feed.title)
57 | except Exception as e:
58 | print(str(e), f"RSS {chat}")
59 | await sleep(RSS_DELAY)
60 |
61 |
62 | loop = get_event_loop()
63 | loop.create_task(rss_worker())
64 |
65 |
66 | @app.on_message(filters.command("add_feed"))
67 | @capture_err
68 | async def add_feed_func(_, m: Message):
69 | if len(m.command) != 2:
70 | return await m.reply("Read 'RSS' section in help menu.")
71 | url = m.text.split(None, 1)[1].strip()
72 |
73 | if not url:
74 | return await m.reply("[ERROR]: Invalid Argument")
75 |
76 | urls = get_urls_from_text(url)
77 | if not urls:
78 | return await m.reply("[ERROR]: Invalid URL")
79 |
80 | url = urls[0]
81 | status = await get_http_status_code(url)
82 | if status != 200:
83 | return await m.reply("[ERROR]: Invalid Url")
84 |
85 | ns = "[ERROR]: This feed isn't supported."
86 | try:
87 | loop = get_event_loop()
88 | parsed = await loop.run_in_executor(None, parse, url)
89 | feed = Feed(parsed)
90 | except Exception:
91 | return await m.reply(ns)
92 | if not feed:
93 | return await m.reply(ns)
94 |
95 | chat_id = m.chat.id
96 | if await is_rss_active(chat_id):
97 | return await m.reply("[ERROR]: You already have an RSS feed enabled.")
98 | try:
99 | await m.reply(feed.parsed(), disable_web_page_preview=True)
100 | except Exception:
101 | return await m.reply(ns)
102 | await add_rss_feed(chat_id, feed.url, feed.title)
103 |
104 |
105 | @app.on_message(filters.command("rm_feed"))
106 | async def rm_feed_func(_, m: Message):
107 | if await is_rss_active(m.chat.id):
108 | await remove_rss_feed(m.chat.id)
109 | await m.reply("Removed RSS Feed")
110 | else:
111 | await m.reply("There are no active RSS Feeds in this chat.")
112 |
--------------------------------------------------------------------------------
/nezuko/modules/sudo.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from pyrogram import filters
25 | from pyrogram.types import Message
26 |
27 | from nezuko import BOT_ID, SUDOERS, app, eor
28 | from nezuko.core.decorators.errors import capture_err
29 | from nezuko.utils.dbfunctions import add_sudo, get_sudoers, remove_sudo
30 | from nezuko.utils.functions import restart
31 |
32 | __MODULE__ = "Sudo"
33 | __HELP__ = """
34 | **THIS MODULE IS ONLY FOR DEVS**
35 |
36 | .useradd - To Add A User In Sudoers.
37 | .userdel - To Remove A User From Sudoers.
38 | .sudoers - To List Sudo Users.
39 |
40 | **NOTE:**
41 |
42 | Never add anyone to sudoers unless you trust them,
43 | sudo users can do anything with your account, they
44 | can even delete your account.
45 | """
46 |
47 |
48 | @app.on_message(filters.command("useradd") & filters.user(SUDOERS))
49 | @capture_err
50 | async def useradd(_, message: Message):
51 | if not message.reply_to_message:
52 | return await eor(
53 | message,
54 | text="Reply to someone's message to add him to sudoers.",
55 | )
56 | user_id = message.reply_to_message.from_user.id
57 | umention = (await app.get_users(user_id)).mention
58 | sudoers = await get_sudoers()
59 | if user_id in sudoers:
60 | return await eor(message, text=f"{umention} is already in sudoers.")
61 | if user_id == BOT_ID:
62 | return await eor(
63 | message, text="You can't add assistant bot in sudoers."
64 | )
65 | added = await add_sudo(user_id)
66 | if added:
67 | await eor(
68 | message,
69 | text=f"Successfully added {umention} in sudoers, Bot will be restarted now.",
70 | )
71 | return await restart(None)
72 | await eor(message, text="Something wrong happened, check logs.")
73 |
74 |
75 | @app.on_message(filters.command("userdel") & filters.user(SUDOERS))
76 | @capture_err
77 | async def userdel(_, message: Message):
78 | if not message.reply_to_message:
79 | return await eor(
80 | message,
81 | text="Reply to someone's message to remove him to sudoers.",
82 | )
83 | user_id = message.reply_to_message.from_user.id
84 | umention = (await app.get_users(user_id)).mention
85 | if user_id not in await get_sudoers():
86 | return await eor(message, text=f"{umention} is not in sudoers.")
87 | removed = await remove_sudo(user_id)
88 | if removed:
89 | await eor(
90 | message,
91 | text=f"Successfully removed {umention} from sudoers, Bot will be restarted now.",
92 | )
93 | return await restart(None)
94 | await eor(message, text="Something wrong happened, check logs.")
95 |
96 |
97 | @app.on_message(filters.command("sudoers") & filters.user(SUDOERS))
98 | @capture_err
99 | async def sudoers_list(_, message: Message):
100 | sudoers = await get_sudoers()
101 | text = ""
102 | for count, user_id in enumerate(sudoers, 1):
103 | user = await app.get_users(user_id)
104 | user = user.first_name if not user.mention else user.mention
105 | text += f"{count}. {user}\n"
106 | await eor(message, text=text)
107 |
--------------------------------------------------------------------------------
/nezuko/modules/telegraph.py:
--------------------------------------------------------------------------------
1 | from pyrogram import filters
2 | from pyrogram.types import Message
3 |
4 | from nezuko import app, telegraph
5 | from nezuko.core.decorators.errors import capture_err
6 |
7 | __MODULE__ = "Telegraph"
8 | __HELP__ = "/telegraph [Page name]: Paste styled text on telegraph."
9 |
10 |
11 | @app.on_message(filters.command("telegraph"))
12 | @capture_err
13 | async def paste(_, message: Message):
14 | reply = message.reply_to_message
15 |
16 | if not reply or not reply.text:
17 | return await message.reply("Reply to a text message")
18 |
19 | if len(message.command) < 2:
20 | return await message.reply("**Usage:**\n /telegraph [Page name]")
21 |
22 | page_name = message.text.split(None, 1)[1]
23 | page = telegraph.create_page(
24 | page_name, html_content=(reply.text.html).replace("\n", "{name}
- This will mention the user with their name.
14 | {chat}
- This will fill with the current chat name.
15 |
16 | NOTE: Fillings only works in greetings module.
17 |
18 |
19 | Supported formatting:
20 |
21 | **Bold**
: Creates bold text.
22 | ~~strike~~
: Creates __italic__
: Creates italic text.
24 | --underline--
: Creates underline text.
25 | `code words`
: Creates code words
text.
26 | [hyperlink](google.com)
: Creates hyperlink text.
27 | Note: You can use both markdown & html tags.
28 |
29 |
30 | Button formatting:
31 |
32 | -> text ~ [button text, button link]
33 |
34 |
35 | Example:
36 |
37 | example button with markdown formatting
~ [button text, https://google.com]
38 | """
39 |
40 |
41 | @app.on_message(command("markdownhelp"))
42 | async def mkdwnhelp(_, m: Message):
43 | keyb = InlineKeyboardMarkup(
44 | [
45 | [
46 | InlineKeyboardButton(
47 | text="Click Here!",
48 | url=f"http://t.me/{BOT_USERNAME}?start=mkdwn_help",
49 | )
50 | ]
51 | ]
52 | )
53 | if m.chat.type != "private":
54 | await m.reply(
55 | "Click on the below button to get markdown usage syntax in pm!",
56 | reply_markup=keyb,
57 | )
58 | else:
59 | await m.reply(
60 | MARKDOWN, parse_mode="html", disable_web_page_preview=True
61 | )
62 | return
63 |
--------------------------------------------------------------------------------
/nezuko/utils/downloader.py:
--------------------------------------------------------------------------------
1 | from os.path import abspath as absolute_path
2 | from time import time
3 |
4 | import aiofiles
5 |
6 | from nezuko import aiohttpsession as session
7 | from nezuko.core.tasks import add_task
8 |
9 |
10 | def ensure_status(status_code: int):
11 | if status_code < 200 or status_code >= 300:
12 | raise Exception(f"HttpProcessingError: {status_code}")
13 |
14 |
15 | async def download_url(
16 | url,
17 | file_path,
18 | chunk_size,
19 | ):
20 | file_path = file_path or url.split("/")[-1][:20]
21 |
22 | async with session.get(url) as response:
23 | ensure_status(response.status)
24 |
25 | async with aiofiles.open(file_path, "wb") as f:
26 |
27 | # Save content in file using aiohttp streamReader.
28 | async for chunk in response.content.iter_chunked(chunk_size):
29 | await f.write(chunk)
30 |
31 | return absolute_path(file_path)
32 |
33 |
34 | async def download(
35 | url: str,
36 | file_path: str = None,
37 | chunk_size: int = 1000000, # 1MB chunk
38 | task_id: int = int(time()),
39 | ):
40 | """
41 | :url: url where the file is located
42 | :file_path: path/to/file
43 | :chunk_size: size of a single chunk
44 |
45 | Returns:
46 | (asyncio.Task, task_id), With which you can await
47 | the task, track task progress or cancel it.
48 | """
49 | # Create a task and add it to main tasks dict
50 | # So we can cancel it using .cancelTask
51 |
52 | task, task_id = await add_task(
53 | download_url,
54 | "Downloader",
55 | url=url,
56 | file_path=file_path,
57 | chunk_size=chunk_size,
58 | )
59 |
60 | return task, task_id
61 |
--------------------------------------------------------------------------------
/nezuko/utils/files.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | import math
25 | import os
26 |
27 | from PIL import Image
28 | from pyrogram import Client, raw
29 | from pyrogram.file_id import FileId
30 |
31 | STICKER_DIMENSIONS = (512, 512)
32 |
33 |
34 | async def resize_file_to_sticker_size(file_path: str) -> str:
35 | im = Image.open(file_path)
36 | if (im.width, im.height) < STICKER_DIMENSIONS:
37 | size1 = im.width
38 | size2 = im.height
39 | if im.width > im.height:
40 | scale = STICKER_DIMENSIONS[0] / size1
41 | size1new = STICKER_DIMENSIONS[0]
42 | size2new = size2 * scale
43 | else:
44 | scale = STICKER_DIMENSIONS[1] / size2
45 | size1new = size1 * scale
46 | size2new = STICKER_DIMENSIONS[1]
47 | size1new = math.floor(size1new)
48 | size2new = math.floor(size2new)
49 | sizenew = (size1new, size2new)
50 | im = im.resize(sizenew)
51 | else:
52 | im.thumbnail(STICKER_DIMENSIONS)
53 | try:
54 | os.remove(file_path)
55 | file_path = f"{file_path}.png"
56 | return file_path
57 | finally:
58 | im.save(file_path)
59 |
60 |
61 | async def upload_document(
62 | client: Client, file_path: str, chat_id: int
63 | ) -> raw.base.InputDocument:
64 | media = await client.invoke(
65 | raw.functions.messages.UploadMedia(
66 | peer=await client.resolve_peer(chat_id),
67 | media=raw.types.InputMediaUploadedDocument(
68 | mime_type=client.guess_mime_type(file_path)
69 | or "application/zip",
70 | file=await client.save_file(file_path),
71 | attributes=[
72 | raw.types.DocumentAttributeFilename(
73 | file_name=os.path.basename(file_path)
74 | )
75 | ],
76 | ),
77 | )
78 | )
79 | return raw.types.InputDocument(
80 | id=media.document.id,
81 | access_hash=media.document.access_hash,
82 | file_reference=media.document.file_reference,
83 | )
84 |
85 |
86 | async def get_document_from_file_id(
87 | file_id: str,
88 | ) -> raw.base.InputDocument:
89 | decoded = FileId.decode(file_id)
90 | return raw.types.InputDocument(
91 | id=decoded.media_id,
92 | access_hash=decoded.access_hash,
93 | file_reference=decoded.file_reference,
94 | )
95 |
--------------------------------------------------------------------------------
/nezuko/utils/filter_groups.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | chat_filters_group = 1
25 | chatbot_group = 2
26 | karma_positive_group = 3
27 | karma_negative_group = 4
28 | regex_group = 5
29 | welcome_captcha_group = 6
30 | antiflood_group = 7
31 | blacklist_filters_group = 8
32 | taglog_group = 9
33 | chat_watcher_group = 10
34 | flood_group = 11
35 | autocorrect_group = 12
36 |
--------------------------------------------------------------------------------
/nezuko/utils/formatter.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 |
25 |
26 | def get_readable_time(seconds: int) -> str:
27 | count = 0
28 | ping_time = ""
29 | time_list = []
30 | time_suffix_list = ["s", "m", "h", "days"]
31 | while count < 4:
32 | count += 1
33 | remainder, result = (
34 | divmod(seconds, 60) if count < 3 else divmod(seconds, 24)
35 | )
36 | if seconds == 0 and remainder == 0:
37 | break
38 | time_list.append(int(result))
39 | seconds = int(remainder)
40 |
41 | for i, _ in enumerate(time_list):
42 | time_list[i] = str(time_list[i]) + time_suffix_list[i]
43 |
44 | if len(time_list) == 4:
45 | ping_time += time_list.pop() + ", "
46 |
47 | time_list.reverse()
48 | ping_time += ":".join(time_list)
49 | return ping_time
50 |
51 |
52 | # Convert seconds to mm:ss
53 | async def convert_seconds_to_minutes(seconds: int):
54 | seconds = int(seconds)
55 | seconds %= 24 * 3600
56 | seconds %= 3600
57 | minutes = seconds // 60
58 | seconds %= 60
59 | return "%02d:%02d" % (minutes, seconds)
60 |
--------------------------------------------------------------------------------
/nezuko/utils/http.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from asyncio import gather
25 |
26 | from nezuko import aiohttpsession as session
27 |
28 |
29 | async def get(url: str, *args, **kwargs):
30 | async with session.get(url, *args, **kwargs) as resp:
31 | try:
32 | data = await resp.json()
33 | except Exception:
34 | data = await resp.text()
35 | return data
36 |
37 |
38 | async def head(url: str, *args, **kwargs):
39 | async with session.head(url, *args, **kwargs) as resp:
40 | try:
41 | data = await resp.json()
42 | except Exception:
43 | data = await resp.text()
44 | return data
45 |
46 |
47 | async def post(url: str, *args, **kwargs):
48 | async with session.post(url, *args, **kwargs) as resp:
49 | try:
50 | data = await resp.json()
51 | except Exception:
52 | data = await resp.text()
53 | return data
54 |
55 |
56 | async def multiget(url: str, times: int, *args, **kwargs):
57 | return await gather(*[get(url, *args, **kwargs) for _ in range(times)])
58 |
59 |
60 | async def multihead(url: str, times: int, *args, **kwargs):
61 | return await gather(*[head(url, *args, **kwargs) for _ in range(times)])
62 |
63 |
64 | async def multipost(url: str, times: int, *args, **kwargs):
65 | return await gather(*[post(url, *args, **kwargs) for _ in range(times)])
66 |
67 |
68 | async def resp_get(url: str, *args, **kwargs):
69 | return await session.get(url, *args, **kwargs)
70 |
71 |
72 | async def resp_post(url: str, *args, **kwargs):
73 | return await session.post(url, *args, **kwargs)
74 |
--------------------------------------------------------------------------------
/nezuko/utils/json_prettify.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 |
25 |
26 | async def json_object_prettify(objecc):
27 | dicc = objecc.__dict__
28 | return "".join(
29 | f"**{key}:** `{value}`\n"
30 | for key, value in dicc.items()
31 | if key not in ["pinned_message", "photo", "_", "_client"]
32 | )
33 |
34 |
35 | async def json_prettify(data):
36 | output = ""
37 | try:
38 | for key, value in data.items():
39 | output += f"**{str(key).capitalize()}:** `{value}`\n"
40 | except Exception:
41 | for datas in data:
42 | for key, value in datas.items():
43 | output += f"**{str(key).capitalize()}:** `{value}`\n"
44 | output += "------------------------\n"
45 | return output
46 |
--------------------------------------------------------------------------------
/nezuko/utils/misc.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from math import ceil
25 |
26 | from pyrogram.types import InlineKeyboardButton
27 |
28 | from nezuko import MOD_LOAD, MOD_NOLOAD
29 |
30 |
31 | class EqInlineKeyboardButton(InlineKeyboardButton):
32 | def __eq__(self, other):
33 | return self.text == other.text
34 |
35 | def __lt__(self, other):
36 | return self.text < other.text
37 |
38 | def __gt__(self, other):
39 | return self.text > other.text
40 |
41 |
42 | def paginate_modules(page_n, module_dict, prefix, chat=None):
43 | if not chat:
44 | modules = sorted(
45 | [
46 | EqInlineKeyboardButton(
47 | x.__MODULE__,
48 | callback_data="{}_module({})".format(
49 | prefix, x.__MODULE__.lower()
50 | ),
51 | )
52 | for x in module_dict.values()
53 | ]
54 | )
55 | else:
56 | modules = sorted(
57 | [
58 | EqInlineKeyboardButton(
59 | x.__MODULE__,
60 | callback_data="{}_module({},{})".format(
61 | prefix, chat, x.__MODULE__.lower()
62 | ),
63 | )
64 | for x in module_dict.values()
65 | ]
66 | )
67 |
68 | pairs = list(zip(modules[::3], modules[1::3], modules[2::3]))
69 | i = 0
70 | for m in pairs:
71 | for _ in m:
72 | i += 1
73 | if len(modules) - i == 1:
74 | pairs.append((modules[-1],))
75 | elif len(modules) - i == 2:
76 | pairs.append(
77 | (
78 | modules[-2],
79 | modules[-1],
80 | )
81 | )
82 |
83 | COLUMN_SIZE = 4
84 |
85 | max_num_pages = ceil(len(pairs) / COLUMN_SIZE)
86 | modulo_page = page_n % max_num_pages
87 |
88 | # can only have a certain amount of buttons side by side
89 | if len(pairs) > COLUMN_SIZE:
90 | pairs = pairs[
91 | modulo_page * COLUMN_SIZE : COLUMN_SIZE * (modulo_page + 1)
92 | ] + [
93 | (
94 | EqInlineKeyboardButton(
95 | "❮",
96 | callback_data="{}_prev({})".format(prefix, modulo_page),
97 | ),
98 | EqInlineKeyboardButton(
99 | "Back",
100 | callback_data="{}_home({})".format(prefix, modulo_page),
101 | ),
102 | EqInlineKeyboardButton(
103 | "❯",
104 | callback_data="{}_next({})".format(prefix, modulo_page),
105 | ),
106 | )
107 | ]
108 |
109 | return pairs
110 |
111 |
112 | def is_module_loaded(name):
113 | return (not MOD_LOAD or name in MOD_LOAD) and name not in MOD_NOLOAD
114 |
--------------------------------------------------------------------------------
/nezuko/utils/pastebin.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from nezuko.utils.http import post
25 |
26 | BASE = "https://batbin.me/"
27 |
28 |
29 | async def paste(content: str):
30 | resp = await post(f"{BASE}api/v2/paste", data=content)
31 | if not resp["success"]:
32 | return
33 | return BASE + resp["message"]
34 |
--------------------------------------------------------------------------------
/nezuko/utils/read_lines.py:
--------------------------------------------------------------------------------
1 | """
2 | MIT License
3 |
4 | Copyright (c) 2021 TheHamkerCat
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | """
24 | from random import choice
25 |
26 |
27 | async def random_line(fname):
28 | with open(fname) as f:
29 | data = f.read().splitlines()
30 | return choice(data)
31 |
--------------------------------------------------------------------------------
/nezuko/utils/rss.py:
--------------------------------------------------------------------------------
1 | class Feed:
2 | def __init__(self, feed):
3 | if not feed.get("entries"):
4 | return
5 | entry = feed["entries"][0]
6 | self.title = entry.get("title") or ""
7 |
8 | # We need title to check latest post
9 | if not self.title:
10 | return
11 |
12 | self.link = entry.get("link") or ""
13 | self.published = entry.get("published") or ""
14 | self.updated = entry.get("updated") or ""
15 | self.author = entry.get("author")
16 | self.summary = entry.get("summary") or ""
17 |
18 | def parsed(self):
19 | text = f"**Title:** [{self.title.strip()}]({self.link or 'https://google.com'})\n"
20 | if self.author:
21 | text += f"**Author:** {self.author}\n"
22 | if self.published:
23 | text += f"**Published:** `{self.published}`\n"
24 | if self.updated:
25 | text += f"**Last Updated:** `{self.updated}`\n"
26 |
27 | if self.summary and "