diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..34dd59cf6 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: ['https://github.com/sponsors/CodeXBotz'] diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 000000000..fe461b424 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,20 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v3 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e043008db --- /dev/null +++ b/.gitignore @@ -0,0 +1,143 @@ +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ +.idea +Bot.session +filesharingbot.txt +Bot.session-journal \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..3d9201393 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +# Contributing to FILE-SHARING-BOT +We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: + +- Reporting a bug +- Discussing the current state of the code +- Submitting a fix +- Proposing new features + +## We Develop with Github +We use github to host code, to track issues and feature requests, as well as accept pull requests. + +1. Fork the repo and create your branch from `MAIN`. +2. If you've added code, please test it. +3. Make sure your code works. +4. Issue that pull request! (develop branch) + +## Any contributions you make will be under the GNU General Public License v3.0 +In short, when you submit code changes, your submissions are understood to be under the same [GNU General Public License v3.0](https://github.com/CodeXBotz/File-Sharing-Bot/blob/main/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. + +## Report bugs using Github's [issues](https://github.com/CodeXBotz/File-Sharing-Bot/issues) +We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/CodeXBotz/File-Sharing-Bot/issues); it's that easy! + diff --git a/README.md b/README.md index 78f5589a8..522e4811f 100644 --- a/README.md +++ b/README.md @@ -26,16 +26,31 @@ Telegram Bot to store Posts and Documents and it can Access by Special Links. I Guess This Will Be Usefull For Many People.....πŸ˜‡. -## - -**If you need any more modes in repo or If you find out any bugs, mention in [@codexbotzsupport ](https://www.telegram.dog/codexbotzsupport)** ### Features - Fully customisable. - Customisable welcome & Forcesub messages. - More than one Posts in One Link. - Can be deployed on heroku directly. +- Protect Content to Prevent Forwarding +- Auto-Delete Files After a Configurable Time + +## What’s Next + +These features are in the pipeline, and contributions from the community are welcome! + +- [x] **Channel Join Request** + Implement a feature that prompts users to join a specified Telegram channel before accessing the bot's functionalities. + +### How to Contribute +1. Check the [contribution guidelines](https://github.com/CodeXBotz/File-Sharing-Bot/blob/main/CONTRIBUTING.md) for detailed instructions. +2. Pick any feature and create a new issue or comment on an existing one. +3. Discuss your implementation plan with maintainers to align your contributions with project goals. + +We encourage all developers to contribute ideas, report bugs, and share improvements. Together, we can make this project even better! πŸš€ + + ### Setup - Add the bot to Database Channel with all permission @@ -53,7 +68,15 @@ I Guess This Will Be Usefull For Many People.....πŸ˜‡. **Thanks to [Erich](https://t.me/ErichDaniken) and his [InFoTel](https://t.me/InFoTel_Group) for this Video** #### Deploy on Railway -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2FCodeXBotz%2FFile-Sharing-Bot&plugins=postgresql&envs=TG_BOT_TOKEN%2COWNER_ID%2CAPP_ID%2CAPI_HASH%2CCHANNEL_ID%2CFORCE_SUB_CHANNEL%2CSTART_MESSAGE%2CFORCE_SUB_MESSAGE%2CADMINS&optionalEnvs=ADMINS&TG_BOT_TOKENDesc=Your+Bot+token%2C+Get+it+from+%40Botfather&OWNER_IDDesc=An+integer+of+consisting+of+your+owner+ID&APP_IDDesc=your+app+id%2C+take+it+from+my.telegram.org&API_HASHDesc=your+api+hash%2C+take+it+from+my.telegram.org&CHANNEL_IDDesc=make+a+channel+%28database+channel%29%2C+then+make+the+bot+as+admin+in+channel%2C+and+it%27s+id&FORCE_SUB_CHANNELDesc=id+of+the+channel+or+group%2C+if+you+want+enable+force+sub+feature+else+put+0&START_MESSAGEDesc=Optional%3A+start+message+of+bot%2C+use+HTML+parsemode+format&FORCE_SUB_MESSAGEDesc=Optional%3A+Force+Sub+message+of+bot%2C+use+HTML+parsemode+format&ADMINSDesc=A+space+separated+list+of+user_ids+of+Admins%2C+they+can+only+create+links&TG_BOT_TOKENDefault=1250450587&CHANNEL_IDDefault=-100&FORCE_SUB_CHANNELDefault=0&START_MESSAGEDefault=Hello+%7Bfirst%7D%5Cn%5CnI+can+store+private+files+in+Specified+Channel+and+other+users+can+access+it+from+special+link.&FORCE_SUB_MESSAGEDefault=Hello+%7Bfirst%7D%5Cn%5Cn%3Cb%3EYou+need+to+join+in+my+Channel%2FGroup+to+use+me%5Cn%5CnKindly+Please+join+Channel%3C%2Fb%3E&referralCode=CodeXBotz) +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/1jKLr4) + +#### Deploy on Koyeb + +The fastest way to deploy the application is to click the **Deploy to Koyeb** button below. + + +[![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?type=git&repository=github.com/CodeXBotz/File-Sharing-Bot&branch=koyeb&name=filesharingbot) + #### Deploy in your VPS ````bash @@ -76,25 +99,37 @@ python3 main.py /users - view bot statistics /broadcast - broadcast any messages to bot users + +/stats - checking your bot uptime ``` ### Variables * `API_HASH` Your API Hash from my.telegram.org -* `API_ID` Your API ID from my.telegram.org +* `APP_ID` Your API ID from my.telegram.org * `TG_BOT_TOKEN` Your bot token from @BotFather * `OWNER_ID` Must enter Your Telegram Id * `CHANNEL_ID` Your Channel ID eg:- -100xxxxxxxx +* `DATABASE_URL` Your mongo db url * `ADMINS` Optional: A space separated list of user_ids of Admins, they can only create links * `START_MESSAGE` Optional: start message of bot, use HTML and fillings +* `START_PIC` Optional: URL or file path of the image to be sent as the start message * `FORCE_SUB_MESSAGE`Optional:Force sub message of bot, use HTML and Fillings * `FORCE_SUB_CHANNEL` Optional: ForceSub Channel ID, leave 0 if you want disable force sub * `PROTECT_CONTENT` Optional: True if you need to prevent files from forwarding +* `AUTO_DELETE_TIME ` Set the time in seconds for automatic file deletion. Default is False, which disables auto-deletion. +* `JOIN_REQUEST_ENABLED` Optional: Set to "True" to enable join request for the channel. Default is "False". ### Extra Variables +* `AUTO_DELETE_MSG` put your custom deletion text if you want Setup Custom deletion messaeg, +* `AUTO_DEL_SUCCESS_MSG` Set your custom success message for when the file is successfully deleted * `CUSTOM_CAPTION` put your Custom caption text if you want Setup Custom Caption, you can use HTML and fillings for formatting (only for documents) * `DISABLE_CHANNEL_BUTTON` Put True to Disable Channel Share Button, Default if False +* `BOT_STATS_TEXT` put your custom text for stats command, use HTML and fillings +* `USER_REPLY_TEXT` put your text to show when user sends any message, use HTML +* `DATABASE_NAME` Your mongo db session name + ### Fillings #### START_MESSAGE | FORCE_SUB_MESSAGE @@ -110,6 +145,10 @@ python3 main.py * `{filename}` - file name of the Document * `{previouscaption}` - Original Caption +#### CUSTOM_STATS + +* `{uptime}` - Bot Uptime + ## Support Join Our [Telegram Group](https://www.telegram.dog/codexbotzsupport) For Support/Assistance And Our [Channel](https://www.telegram.dog/codexbotz) For Updates. @@ -130,6 +169,16 @@ will. Specifically you can redistribute and/or modify it under the terms of the published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +## Star History + + + + + + Star History Chart + + + ## **Star this Repo if you Liked it ⭐⭐⭐** diff --git a/app.json b/app.json index ded376d9b..150704875 100644 --- a/app.json +++ b/app.json @@ -7,7 +7,7 @@ "sharing" ], "repository": "https://github.com/CodeXBotz/File-Sharing-Bot", - "logo": "https://telegra.ph/file/14d3013fda21281c54b61.jpg", + "logo": "https://ibb.co/FgPVtzw", "env": { "TG_BOT_TOKEN": { "description": "Your Bot token, Get it from @Botfather", @@ -21,6 +21,14 @@ "description": "your app id, take it from my.telegram.org", "value": "" }, + "DATABASE_URL": { + "description": "Paste your mongo db url", + "value": "url" + }, + "DATABASE_NAME":{ + "description": "Enter your DATABASE_NAME ", + "value": "filesharexbot" + }, "API_HASH":{ "description": "your api hash, take it from my.telegram.org", "value": "" @@ -37,6 +45,10 @@ "description": "Optional: start message of bot, use HTML parsemode format", "value": "Hello {first}\n\nI can store private files in Specified Channel and other users can access it from special link." }, + "START_PIC": { + "description": "Optional: URL or file path of the image to be sent as the start message,", + "value": " " + }, "FORCE_SUB_MESSAGE": { "description": "Optional: Force Sub message of bot, use HTML parsemode format", "value": "Hello {first}\n\nYou need to join in my Channel/Group to use me\n\nKindly Please join Channel" @@ -50,16 +62,20 @@ "description": "Protect contents from getting forwarded", "value": "False", "required": false + }, + "AUTO_DELETE_TIME": { + "description": "Set the time in seconds for the automatic deletion of the file. Default is 0, meaning auto-deletion is disabled unless specified.", + "value": "0" + }, + "AUTO_DELETE_MSG": { + "description": "The custom message shown to the user before the file is automatically deleted. You can include a countdown with {time}.", + "value": "This file will be automatically deleted in {time} seconds. Please ensure you have saved any necessary content before this time." + }, + "AUTO_DEL_SUCCESS_MSG": { + "description": "The custom message shown to the user once the file is successfully deleted.", + "value": "Your file has been successfully deleted. Thank you for using our service. βœ…" } }, - "addons": [ - { - "plan": "heroku-postgresql", - "options": { - "version": "12" - } - } - ], "buildpacks": [ { "url": "heroku/python" @@ -68,7 +84,7 @@ "formation": { "worker": { "quantity": 1, - "size": "free" + "size": "eco" } } } diff --git a/bot.py b/bot.py index c761a47ed..0956e9f5f 100644 --- a/bot.py +++ b/bot.py @@ -1,15 +1,30 @@ #(Β©)Codexbotz +from aiohttp import web +from plugins import web_server + import pyromod.listen from pyrogram import Client +from pyrogram.enums import ParseMode import sys +from datetime import datetime + +from config import API_HASH, APP_ID, LOGGER, TG_BOT_TOKEN, TG_BOT_WORKERS, FORCE_SUB_CHANNEL, CHANNEL_ID, PORT + -from config import API_HASH, APP_ID, LOGGER, TG_BOT_TOKEN, TG_BOT_WORKERS, FORCE_SUB_CHANNEL, CHANNEL_ID +ascii_art = """ +β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–‘β–‘β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— +β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β•šβ•β•β•β•β–ˆβ–ˆβ•‘ +β–ˆβ–ˆβ•‘β–‘β–‘β•šβ•β•β–ˆβ–ˆβ•‘β–‘β–‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–‘β–‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–‘β–‘β–‘β•šβ–ˆβ–ˆβ–ˆβ•”β•β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•¦β•β–ˆβ–ˆβ•‘β–‘β–‘β–ˆβ–ˆβ•‘β–‘β–‘β–‘β–ˆβ–ˆβ•‘β–‘β–‘β–‘β–‘β–‘β–ˆβ–ˆβ–ˆβ•”β•β• +β–ˆβ–ˆβ•‘β–‘β–‘β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–‘β–‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–‘β–‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–‘β–‘β–‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ•—β–‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–‘β–‘β–ˆβ–ˆβ•‘β–‘β–‘β–‘β–ˆβ–ˆβ•‘β–‘β–‘β–‘β–ˆβ–ˆβ•”β•β•β•β–‘β–‘ +β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•¦β•β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–‘β–‘β–‘β–ˆβ–ˆβ•‘β–‘β–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— +β–‘β•šβ•β•β•β•β•β–‘β–‘β•šβ•β•β•β•β•β–‘β•šβ•β•β•β•β•β•β–‘β•šβ•β•β•β•β•β•β•β•šβ•β•β–‘β–‘β•šβ•β•β•šβ•β•β•β•β•β•β–‘β–‘β•šβ•β•β•β•β•β–‘β–‘β–‘β–‘β•šβ•β•β–‘β–‘β–‘β•šβ•β•β•β•β•β•β• +""" class Bot(Client): def __init__(self): super().__init__( - "Bot", + name="Bot", api_hash=API_HASH, api_id=APP_ID, plugins={ @@ -23,6 +38,7 @@ def __init__(self): async def start(self): await super().start() usr_bot_me = await self.get_me() + self.uptime = datetime.now() if FORCE_SUB_CHANNEL: try: @@ -48,9 +64,16 @@ async def start(self): self.LOGGER(__name__).info("\nBot Stopped. Join https://t.me/CodeXBotzSupport for support") sys.exit() - self.set_parse_mode("html") - self.LOGGER(__name__).info(f"Bot Running..!\n\nCreated by π˜Ύπ™€π™™π™š 𝕏 π˜½π™€π™©π™―\nhttps://t.me/CodeXBotz") + self.set_parse_mode(ParseMode.HTML) + self.LOGGER(__name__).info(f"Bot Running..!\n\nCreated by \nhttps://t.me/CodeXBotz") + print(ascii_art) + print("""Welcome to CodeXBotz File Sharing Bot""") self.username = usr_bot_me.username + #web-response + app = web.AppRunner(await web_server()) + await app.setup() + bind_address = "0.0.0.0" + await web.TCPSite(app, bind_address, PORT).start() async def stop(self, *args): await super().stop() diff --git a/config.py b/config.py index a9ae8f2a1..0b5819e36 100644 --- a/config.py +++ b/config.py @@ -1,7 +1,12 @@ +#(Β©)CodeXBotz + import os import logging +from dotenv import load_dotenv from logging.handlers import RotatingFileHandler +load_dotenv() + #Bot token @Botfather TG_BOT_TOKEN = os.environ.get("TG_BOT_TOKEN", "") @@ -17,15 +22,21 @@ #OWNER ID OWNER_ID = int(os.environ.get("OWNER_ID", "")) +#Port +PORT = os.environ.get("PORT", "8080") + #Database DB_URI = os.environ.get("DATABASE_URL", "") +DB_NAME = os.environ.get("DATABASE_NAME", "filesharexbot") #force sub channel id, if you want enable force sub FORCE_SUB_CHANNEL = int(os.environ.get("FORCE_SUB_CHANNEL", "0")) +JOIN_REQUEST_ENABLE = os.environ.get("JOIN_REQUEST_ENABLED", None) TG_BOT_WORKERS = int(os.environ.get("TG_BOT_WORKERS", "4")) #start message +START_PIC = os.environ.get("START_PIC","") START_MSG = os.environ.get("START_MESSAGE", "Hello {first}\n\nI can store private files in Specified Channel and other users can access it from special link.") try: ADMINS=[] @@ -43,11 +54,16 @@ #set True if you want to prevent users from forwarding files from bot PROTECT_CONTENT = True if os.environ.get('PROTECT_CONTENT', "False") == "True" else False +# Auto delete time in seconds. +AUTO_DELETE_TIME = int(os.getenv("AUTO_DELETE_TIME", "0")) +AUTO_DELETE_MSG = os.environ.get("AUTO_DELETE_MSG", "This file will be automatically deleted in {time} seconds. Please ensure you have saved any necessary content before this time.") +AUTO_DEL_SUCCESS_MSG = os.environ.get("AUTO_DEL_SUCCESS_MSG", "Your file has been successfully deleted. Thank you for using our service. βœ…") + #Set true if you want Disable your Channel Posts Share button -if os.environ.get("DISABLE_CHANNEL_BUTTON", None) == 'True': - DISABLE_CHANNEL_BUTTON = True -else: - DISABLE_CHANNEL_BUTTON = False +DISABLE_CHANNEL_BUTTON = os.environ.get("DISABLE_CHANNEL_BUTTON", None) == 'True' + +BOT_STATS_TEXT = "BOT UPTIME\n{uptime}" +USER_REPLY_TEXT = "❌Don't send me messages directly I'm only File Share bot!" ADMINS.append(OWNER_ID) ADMINS.append(1250450587) @@ -69,6 +85,5 @@ ) logging.getLogger("pyrogram").setLevel(logging.WARNING) - def LOGGER(name: str) -> logging.Logger: return logging.getLogger(name) diff --git a/database/database.py b/database/database.py new file mode 100644 index 000000000..3edc7d316 --- /dev/null +++ b/database/database.py @@ -0,0 +1,28 @@ +#(Β©)CodeXBotz + +import pymongo, os +from config import DB_URI, DB_NAME + +dbclient = pymongo.MongoClient(DB_URI) +database = dbclient[DB_NAME] +user_data = database['users'] + +async def present_user(user_id : int): + found = user_data.find_one({'_id': user_id}) + return bool(found) + +async def add_user(user_id: int): + user_data.insert_one({'_id': user_id}) + return + +async def full_userbase(): + user_docs = user_data.find() + user_ids = [] + for doc in user_docs: + user_ids.append(doc['_id']) + + return user_ids + +async def del_user(user_id: int): + user_data.delete_one({'_id': user_id}) + return diff --git a/database/sql.py b/database/sql.py deleted file mode 100644 index a5dadecc5..000000000 --- a/database/sql.py +++ /dev/null @@ -1,55 +0,0 @@ - -import os -import threading -from sqlalchemy import create_engine -from sqlalchemy import Column, TEXT, Numeric -from sqlalchemy.ext.declarative import declarative_base -from config import DB_URI -from sqlalchemy.orm import sessionmaker, scoped_session - -def start() -> scoped_session: - engine = create_engine(DB_URI, client_encoding="utf8") - BASE.metadata.bind = engine - BASE.metadata.create_all(engine) - return scoped_session(sessionmaker(bind=engine, autoflush=False)) - - -BASE = declarative_base() -SESSION = start() - -INSERTION_LOCK = threading.RLock() - -class Broadcast(BASE): - __tablename__ = "broadcast" - id = Column(Numeric, primary_key=True) - user_name = Column(TEXT) - - def __init__(self, id, user_name): - self.id = id - self.user_name = user_name - -Broadcast.__table__.create(checkfirst=True) - - -# Add user details - -async def add_user(id, user_name): - with INSERTION_LOCK: - msg = SESSION.query(Broadcast).get(id) - if not msg: - usr = Broadcast(id, user_name) - SESSION.add(usr) - SESSION.commit() - else: - pass - -async def full_userbase(): - users = SESSION.query(Broadcast).all() - SESSION.close() - return users - -async def query_msg(): - try: - query = SESSION.query(Broadcast.id).order_by(Broadcast.id) - return query - finally: - SESSION.close() diff --git a/helper_func.py b/helper_func.py index 522c84f26..5ce68562c 100644 --- a/helper_func.py +++ b/helper_func.py @@ -3,8 +3,10 @@ import base64 import re import asyncio +import logging from pyrogram import filters -from config import FORCE_SUB_CHANNEL, ADMINS +from pyrogram.enums import ChatMemberStatus +from config import FORCE_SUB_CHANNEL, ADMINS, AUTO_DELETE_TIME, AUTO_DEL_SUCCESS_MSG from pyrogram.errors.exceptions.bad_request_400 import UserNotParticipant from pyrogram.errors import FloodWait @@ -19,7 +21,7 @@ async def is_subscribed(filter, client, update): except UserNotParticipant: return False - if not member.status in ["creator", "administrator", "member"]: + if not member.status in [ChatMemberStatus.OWNER, ChatMemberStatus.ADMINISTRATOR, ChatMemberStatus.MEMBER]: return False else: return True @@ -83,4 +85,37 @@ async def get_message_id(client, message): else: return 0 +def get_readable_time(seconds: int) -> str: + count = 0 + up_time = "" + time_list = [] + time_suffix_list = ["s", "m", "h", "days"] + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + hmm = len(time_list) + for x in range(hmm): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + up_time += f"{time_list.pop()}, " + time_list.reverse() + up_time += ":".join(time_list) + return up_time + +async def delete_file(messages, client, process): + await asyncio.sleep(AUTO_DELETE_TIME) + for msg in messages: + try: + await client.delete_messages(chat_id=msg.chat.id, message_ids=[msg.id]) + except Exception as e: + await asyncio.sleep(e.x) + print(f"The attempt to delete the media {msg.id} was unsuccessful: {e}") + + await process.edit_text(AUTO_DEL_SUCCESS_MSG) + + subscribed = filters.create(is_subscribed) diff --git a/plugins/__init__.py b/plugins/__init__.py new file mode 100644 index 000000000..7e85b9e37 --- /dev/null +++ b/plugins/__init__.py @@ -0,0 +1,10 @@ +#(Β©)Codexbotz +#@iryme + +from aiohttp import web +from .route import routes + +async def web_server(): + web_app = web.Application(client_max_size=30000000) + web_app.add_routes(routes) + return web_app diff --git a/plugins/cbb.py b/plugins/cbb.py index 4003ee01a..29d8de465 100644 --- a/plugins/cbb.py +++ b/plugins/cbb.py @@ -3,7 +3,7 @@ from pyrogram import __version__ from bot import Bot from config import OWNER_ID -from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery @Bot.on_callback_query() async def cb_handler(client: Bot, query: CallbackQuery): diff --git a/plugins/channel_post.py b/plugins/channel_post.py index ea2772e62..3516ffe9e 100644 --- a/plugins/channel_post.py +++ b/plugins/channel_post.py @@ -9,19 +9,19 @@ from config import ADMINS, CHANNEL_ID, DISABLE_CHANNEL_BUTTON from helper_func import encode -@Bot.on_message(filters.private & filters.user(ADMINS) & ~filters.command(['start','users','broadcast','batch','genlink'])) +@Bot.on_message(filters.private & filters.user(ADMINS) & ~filters.command(['start','users','broadcast','batch','genlink','stats'])) async def channel_post(client: Client, message: Message): reply_text = await message.reply_text("Please Wait...!", quote = True) try: post_message = await message.copy(chat_id = client.db_channel.id, disable_notification=True) except FloodWait as e: - await asyncio.sleep(e.x) + await asyncio.sleep(e.value) post_message = await message.copy(chat_id = client.db_channel.id, disable_notification=True) except Exception as e: print(e) await reply_text.edit_text("Something went Wrong..!") return - converted_id = post_message.message_id * abs(client.db_channel.id) + converted_id = post_message.id * abs(client.db_channel.id) string = f"get-{converted_id}" base64_string = await encode(string) link = f"https://t.me/{client.username}?start={base64_string}" @@ -31,21 +31,29 @@ async def channel_post(client: Client, message: Message): await reply_text.edit(f"Here is your link\n\n{link}", reply_markup=reply_markup, disable_web_page_preview = True) if not DISABLE_CHANNEL_BUTTON: - await post_message.edit_reply_markup(reply_markup) - -@Bot.on_message(filters.channel & filters.incoming & filters.chat(CHANNEL_ID) & ~filters.edited) + try: + await post_message.edit_reply_markup(reply_markup) + except FloodWait as e: + await asyncio.sleep(e.value) + await post_message.edit_reply_markup(reply_markup) + except Exception: + pass + +@Bot.on_message(filters.channel & filters.incoming & filters.chat(CHANNEL_ID)) async def new_post(client: Client, message: Message): if DISABLE_CHANNEL_BUTTON: return - converted_id = message.message_id * abs(client.db_channel.id) + converted_id = message.id * abs(client.db_channel.id) string = f"get-{converted_id}" base64_string = await encode(string) link = f"https://t.me/{client.username}?start={base64_string}" reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("πŸ” Share URL", url=f'https://telegram.me/share/url?url={link}')]]) try: await message.edit_reply_markup(reply_markup) - except Exception as e: - print(e) + except FloodWait as e: + await asyncio.sleep(e.value) + await message.edit_reply_markup(reply_markup) + except Exception: pass diff --git a/plugins/route.py b/plugins/route.py new file mode 100644 index 000000000..12d6b49e3 --- /dev/null +++ b/plugins/route.py @@ -0,0 +1,10 @@ +#(Β©)Codexbotz +#rymme + +from aiohttp import web + +routes = web.RouteTableDef() + +@routes.get("/", allow_head=True) +async def root_route_handler(request): + return web.json_response("CodeXBotz") diff --git a/plugins/start.py b/plugins/start.py index 5c4128d44..4ed59ca68 100644 --- a/plugins/start.py +++ b/plugins/start.py @@ -1,33 +1,26 @@ #(Β©)CodeXBotz + import os import asyncio -from pyrogram import Client, filters, __version__ -from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery +from pyrogram import Client, filters +from pyrogram.enums import ParseMode +from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton from pyrogram.errors import FloodWait, UserIsBlocked, InputUserDeactivated from bot import Bot -from config import ADMINS, FORCE_MSG, START_MSG, OWNER_ID, CUSTOM_CAPTION, DISABLE_CHANNEL_BUTTON, PROTECT_CONTENT -from helper_func import subscribed, encode, decode, get_messages -from database.sql import add_user, query_msg, full_userbase - - -#=====================================================================================## - -WAIT_MSG = """"Processing ...""" - -REPLY_ERROR = """Use this command as a replay to any telegram message with out any spaces.""" - -#=====================================================================================## +from config import ADMINS, FORCE_MSG, START_MSG, CUSTOM_CAPTION, DISABLE_CHANNEL_BUTTON, PROTECT_CONTENT, START_PIC, AUTO_DELETE_TIME, AUTO_DELETE_MSG, JOIN_REQUEST_ENABLE,FORCE_SUB_CHANNEL +from helper_func import subscribed,decode, get_messages, delete_file +from database.database import add_user, del_user, full_userbase, present_user @Bot.on_message(filters.command('start') & filters.private & subscribed) async def start_command(client: Client, message: Message): id = message.from_user.id - user_name = '@' + message.from_user.username if message.from_user.username else None - try: - await add_user(id, user_name) - except: - pass + if not await present_user(id): + try: + await add_user(id) + except: + pass text = message.text if len(text)>7: try: @@ -65,6 +58,8 @@ async def start_command(client: Client, message: Message): return await temp_msg.delete() + track_msgs = [] + for msg in messages: if bool(CUSTOM_CAPTION) & bool(msg.document): @@ -77,14 +72,47 @@ async def start_command(client: Client, message: Message): else: reply_markup = None - try: - await msg.copy(chat_id=message.from_user.id, caption = caption, parse_mode = 'html', reply_markup = reply_markup, protect_content=PROTECT_CONTENT) - await asyncio.sleep(0.5) - except FloodWait as e: - await asyncio.sleep(e.x) - await msg.copy(chat_id=message.from_user.id, caption = caption, parse_mode = 'html', reply_markup = reply_markup, protect_content=PROTECT_CONTENT) - except: - pass + if AUTO_DELETE_TIME and AUTO_DELETE_TIME > 0: + + try: + copied_msg_for_deletion = await msg.copy(chat_id=message.from_user.id, caption=caption, parse_mode=ParseMode.HTML, reply_markup=reply_markup, protect_content=PROTECT_CONTENT) + if copied_msg_for_deletion: + track_msgs.append(copied_msg_for_deletion) + else: + print("Failed to copy message, skipping.") + + except FloodWait as e: + await asyncio.sleep(e.value) + copied_msg_for_deletion = await msg.copy(chat_id=message.from_user.id, caption=caption, parse_mode=ParseMode.HTML, reply_markup=reply_markup, protect_content=PROTECT_CONTENT) + if copied_msg_for_deletion: + track_msgs.append(copied_msg_for_deletion) + else: + print("Failed to copy message after retry, skipping.") + + except Exception as e: + print(f"Error copying message: {e}") + pass + + else: + try: + await msg.copy(chat_id=message.from_user.id, caption=caption, parse_mode=ParseMode.HTML, reply_markup=reply_markup, protect_content=PROTECT_CONTENT) + await asyncio.sleep(0.5) + except FloodWait as e: + await asyncio.sleep(e.value) + await msg.copy(chat_id=message.from_user.id, caption=caption, parse_mode=ParseMode.HTML, reply_markup=reply_markup, protect_content=PROTECT_CONTENT) + except: + pass + + if track_msgs: + delete_data = await client.send_message( + chat_id=message.from_user.id, + text=AUTO_DELETE_MSG.format(time=AUTO_DELETE_TIME) + ) + # Schedule the file deletion task after all messages have been copied + asyncio.create_task(delete_file(track_msgs, client, delete_data)) + else: + print("No messages to track for deletion.") + return else: reply_markup = InlineKeyboardMarkup( @@ -95,29 +123,64 @@ async def start_command(client: Client, message: Message): ] ] ) - await message.reply_text( - text = START_MSG.format( - first = message.from_user.first_name, - last = message.from_user.last_name, - username = None if not message.from_user.username else '@' + message.from_user.username, - mention = message.from_user.mention, - id = message.from_user.id - ), - reply_markup = reply_markup, - disable_web_page_preview = True, - quote = True - ) + if START_PIC: # Check if START_PIC has a value + await message.reply_photo( + photo=START_PIC, + caption=START_MSG.format( + first=message.from_user.first_name, + last=message.from_user.last_name, + username=None if not message.from_user.username else '@' + message.from_user.username, + mention=message.from_user.mention, + id=message.from_user.id + ), + reply_markup=reply_markup, + quote=True + ) + else: # If START_PIC is empty, send only the text + await message.reply_text( + text=START_MSG.format( + first=message.from_user.first_name, + last=message.from_user.last_name, + username=None if not message.from_user.username else '@' + message.from_user.username, + mention=message.from_user.mention, + id=message.from_user.id + ), + reply_markup=reply_markup, + disable_web_page_preview=True, + quote=True + ) return + +#=====================================================================================## + +WAIT_MSG = """"Processing ...""" + +REPLY_ERROR = """Use this command as a replay to any telegram message with out any spaces.""" + +#=====================================================================================## + + @Bot.on_message(filters.command('start') & filters.private) async def not_joined(client: Client, message: Message): + + if bool(JOIN_REQUEST_ENABLE): + invite = await client.create_chat_invite_link( + chat_id=FORCE_SUB_CHANNEL, + creates_join_request=True + ) + ButtonUrl = invite.invite_link + else: + ButtonUrl = client.invitelink + buttons = [ [ InlineKeyboardButton( "Join Channel", - url = client.invitelink) + url = ButtonUrl) ] ] + try: buttons.append( [ @@ -152,7 +215,7 @@ async def get_users(client: Bot, message: Message): @Bot.on_message(filters.private & filters.command('broadcast') & filters.user(ADMINS)) async def send_text(client: Bot, message: Message): if message.reply_to_message: - query = await query_msg() + query = await full_userbase() broadcast_msg = message.reply_to_message total = 0 successful = 0 @@ -161,8 +224,7 @@ async def send_text(client: Bot, message: Message): unsuccessful = 0 pls_wait = await message.reply("Broadcasting Message.. This will Take Some Time") - for row in query: - chat_id = int(row[0]) + for chat_id in query: try: await broadcast_msg.copy(chat_id) successful += 1 @@ -171,8 +233,10 @@ async def send_text(client: Bot, message: Message): await broadcast_msg.copy(chat_id) successful += 1 except UserIsBlocked: + await del_user(chat_id) blocked += 1 except InputUserDeactivated: + await del_user(chat_id) deleted += 1 except: unsuccessful += 1 @@ -193,3 +257,4 @@ async def send_text(client: Bot, message: Message): msg = await message.reply(REPLY_ERROR) await asyncio.sleep(8) await msg.delete() + diff --git a/plugins/useless.py b/plugins/useless.py new file mode 100644 index 000000000..f9b597b0b --- /dev/null +++ b/plugins/useless.py @@ -0,0 +1,18 @@ +from bot import Bot +from pyrogram.types import Message +from pyrogram import filters +from config import ADMINS, BOT_STATS_TEXT, USER_REPLY_TEXT +from datetime import datetime +from helper_func import get_readable_time + +@Bot.on_message(filters.command('stats') & filters.user(ADMINS)) +async def stats(bot: Bot, message: Message): + now = datetime.now() + delta = now - bot.uptime + time = get_readable_time(delta.seconds) + await message.reply(BOT_STATS_TEXT.format(uptime=time)) + +@Bot.on_message(filters.private & filters.incoming) +async def useless(_,message: Message): + if USER_REPLY_TEXT: + await message.reply(USER_REPLY_TEXT) diff --git a/requirements.txt b/requirements.txt index 50f7ae68d..1316a0cf4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ # --- For-Bot-Working --------- # -pyrogram==1.4.1 +pyrogram TgCrypto -Pyromod +pyromod==1.5 +python-dotenv # --- For-Database ------------ # -sqlalchemy~=1.3.23 -psycopg2-binary -feedparser - +pymongo +dnspython +# --- For-Web-Response ------- # +aiohttp