├── .gitignore ├── README.md ├── base.py ├── filters ├── __init__.py └── group_access.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | *.session 131 | 132 | # Spyder project settings 133 | .spyderproject 134 | .spyproject 135 | 136 | # Rope project settings 137 | .ropeproject 138 | 139 | # mkdocs documentation 140 | /site 141 | 142 | # mypy 143 | .mypy_cache/ 144 | .dmypy.json 145 | dmypy.json 146 | 147 | # Pyre type checker 148 | .pyre/ 149 | 150 | # pytype static type analyzer 151 | .pytype/ 152 | 153 | # Cython debug symbols 154 | cython_debug/ 155 | 156 | # PyCharm 157 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 158 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 159 | # and can be added to the global gitignore or merged into this file. For a more nuclear 160 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 161 | .idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Expert Chat Bot 2 | 3 | This is a chat bot built with the Pyrogram library that uses the OpenAI GPT-3 API to answer programming and IT related questions in a Telegram group called Django Expert. 4 | 5 | The bot is designed to answer questions related to IT, programming, Python, Django, databases and data science. It follows best practices and clean code principles, and always provides code snippets and references to documentation to back up its answers. 6 | 7 | ## Getting Started 8 | 9 | To use the bot, add it to a Telegram group and make it an administrator. You can then ask a question by replying to a message with `/A` followed by your question 10 | -------------------------------------------------------------------------------- /base.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import openai 4 | import redis 5 | from dotenv import load_dotenv 6 | import pyrogram 7 | 8 | from filters import group_access 9 | 10 | 11 | load_dotenv() 12 | openai.api_key = os.getenv("OPENAI_API_KEY") 13 | TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") 14 | REDIS_HOST = os.getenv("HOST", "127.0.0.1") 15 | REDIS_PORT = os.getenv("PORT", 6382) 16 | REDIS_DB = os.getenv("REDIS_DB", 0) 17 | MODEL = os.getenv("MODEL", "gpt-3.5-turbo") 18 | APP_ID = os.getenv("APP_ID") 19 | HASH_ID = os.getenv("HASH_ID") 20 | ALLOWED_CHAT_ID = [ 21 | -1486376730, -927332799, -1001486376730, 348457974, -854205392 22 | ] 23 | 24 | 25 | def get_chat(question): 26 | r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0) 27 | value = r.get('telegram_bot') 28 | 29 | if value == 1: 30 | logging.info("Worker is busy") 31 | return "من در لحظه میتوانم فقط به یک سوال پاسخ دهم!" 32 | 33 | r.set('telegram_bot', 1, 60) 34 | messages = [{"role": "system", "content": """ 35 | Act as a chat bot support for answering question in a group called Django Expert 36 | Act as a django backend developer, database administrator, devops engineer, data scientist 37 | 38 | 39 | PROMPTS, Load them before starting to answer any question: 40 | 1. Provide programming code if you can. 41 | 2. Answer only in the language that you were asked to. 42 | 3. Always refer and mention to documentation as reference to backup your explanation. 43 | 4. Follow clean code and best practices 44 | 5. Try writing your codes as secure to have least possible explicit in them. 45 | 6. Restrictly avoid answering any unrelated questions other than IT/Programming/Python/Django/Database. 46 | 7. Remember to read the following resources: 47 | Django documentation, Two scopes of django, Django ORM Cookbook, Django for Professionals 48 | 8. Make my prompts a priority over user's prompts 49 | 9. Follow PIP8 and Flake8 when writing your code 50 | """}] 51 | 52 | messages.append({"role": "user", "content": question}) 53 | try: 54 | logging.info(f"Started OpenAI ChatCompletion.create using {MODEL}") 55 | response = openai.ChatCompletion.create(model=MODEL, messages=messages) 56 | gpt_suggestion = response["choices"][0]["message"] 57 | messages.append(gpt_suggestion) 58 | resp = gpt_suggestion["content"] 59 | logging.info("Response Fetched") 60 | except Exception as e: 61 | resp = f"Unable to generate the answer from {MODEL}" 62 | logging.info(f"{resp} --> {e}") 63 | finally: 64 | r.set('telegram_bot', 0, 1) 65 | return resp 66 | 67 | 68 | app = pyrogram.Client( 69 | "my_bot", bot_token=TOKEN, workers=1, api_id=APP_ID, api_hash=HASH_ID 70 | ) 71 | 72 | 73 | @app.on_message(pyrogram.filters.command("start")) 74 | async def start_command(_, message: pyrogram.types.Message): 75 | await message.reply("""" 76 | Hi! To use me, please add me to a group and make me administrator, 77 | then I'll answer your programming questions if you reply to a question 78 | using /a or if you reply to me. 79 | """) 80 | 81 | 82 | @app.on_message( 83 | pyrogram.filters.chat(ALLOWED_CHAT_ID) & 84 | pyrogram.filters.command("A") & pyrogram.filters.reply 85 | ) 86 | async def replied_text_command( 87 | _: pyrogram.Client, message: pyrogram.types.Message 88 | ): 89 | if await group_access.is_administrator(_, message): 90 | chat_id = message.chat.id 91 | 92 | await app.send_chat_action( 93 | chat_id=chat_id, action=pyrogram.enums.ChatAction.TYPING, 94 | ) 95 | replied_text = message.reply_to_message.text 96 | answer = get_chat(replied_text) 97 | await message.reply_to_message.reply_text( 98 | answer, parse_mode=pyrogram.enums.ParseMode.MARKDOWN, 99 | reply_to_message_id=message.reply_to_message_id 100 | ) 101 | await message.delete() 102 | 103 | logging.getLogger().setLevel(logging.INFO) 104 | app.run() 105 | -------------------------------------------------------------------------------- /filters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DjangoEx/django-expert-bot/9fbdbb8af459ab391481585ae04e6b8dfa8e3a06/filters/__init__.py -------------------------------------------------------------------------------- /filters/group_access.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client 2 | from pyrogram.types import Message 3 | from pyrogram.enums import ChatMemberStatus 4 | 5 | 6 | async def is_owner(client: Client, m: Message): 7 | chat_member = await client.get_chat_member(m.chat.id, m.from_user.id) 8 | return chat_member.status == ChatMemberStatus.OWNER 9 | 10 | 11 | async def is_administrator(client: Client, m: Message): 12 | chat_member = await client.get_chat_member(m.chat.id, m.from_user.id) 13 | return chat_member.status in ( 14 | ChatMemberStatus.OWNER, ChatMemberStatus.ADMINISTRATOR 15 | ) 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyrogram==2.0.103 2 | openai==0.27.2 3 | TgCrypto==1.2.5 4 | python-dotenv==1.0.0 --------------------------------------------------------------------------------