├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug-report---.md │ ├── config.yml │ └── feature-request---.md └── workflows │ └── stale.yml ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── about.jpg ├── bmac.png └── logo.png ├── config.py ├── main.py ├── requirements.txt └── resources ├── lang_emojis.py ├── markups_handler.py ├── msg_handler.py └── mysql_handler.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [fabston] 2 | liberapay: fabston -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report---.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug report \U0001F41E" 3 | about: Create a bug report 4 | labels: bug 5 | 6 | --- 7 | 8 | ## Describe the bug 9 | A clear and concise description of what the bug is. 10 | 11 | ### Steps to reproduce 12 | Steps to reproduce the behavior. 13 | 14 | ### Expected behavior 15 | A clear and concise description of what you expected to happen. 16 | 17 | ### Environment 18 | - OS: [e.g. Arch Linux] 19 | - Other details that you think may affect. 20 | 21 | ### Additional context 22 | Add any other context about the problem here. 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Question 🙋 3 | url: https://github.com/fabston/Telegram-Support-Bot/discussions/categories/q-a 4 | about: Ask your question in our Discussions Channel -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request---.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Feature request \U0001F680" 3 | about: Suggest an idea 4 | labels: enhancement 5 | 6 | --- 7 | 8 | ## Summary 9 | Brief explanation of the feature. 10 | 11 | ### Basic example 12 | Include a basic example or links here. 13 | 14 | ### Motivation 15 | Why are we doing this? What use cases does it support? What is the expected outcome? 16 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark and close stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | stale: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/stale@v1 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | stale-issue-message: 'It looks like this has been idle a while, so I am marking it as stale. Remove the label or comment if this issue should remain open.' 17 | stale-pr-message: 'It looks like this PR has been idle a while, so I am marking it as stale. Remove the label or comment if this is still being worked on.' 18 | stale-issue-label: 'no-activity' 19 | stale-pr-label: 'no-activity' 20 | exempt-pr-labels: 'help wanted', 'enhancement' 21 | days-before-stale: 30 22 | days-before-close: 5 -------------------------------------------------------------------------------- /.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 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 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 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2021 fabston 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | Python version 5 | GitHub license 6 | GitHub issues 7 | GitHub pull requests 8 |
GitHub stars 9 | GitHub forks 10 | GitHub watchers 11 |

12 | 13 |

14 | About 15 | • 16 | Features 17 | • 18 | Installation 19 | • 20 | Images 21 | • 22 | Help 23 |

24 | 25 | ## About 26 | The **Telegram Support Bot** 📬 helps you to manage and organize your support inquiries. 27 | 28 | ## Features 29 | - **Text**, **Photos**, **Documents** and **Stickers** are being forwarded 30 | - Spam protection (sensitivity can be set in [`config.py`](https://github.com/fabston/Telegram-Support-Bot/blob/master/config.py)) 31 | - Bad words filter (using regex, words can be set in [`config.py`](https://github.com/fabston/Telegram-Support-Bot/blob/master/config.py)) 32 | - List all open/unanswered tickets (time passed since ticket opened is being shown as well) 33 | - Ban / Un-ban users (via reply or user id). User won't be able to interact with the bot anymore 34 | - List banned users, with last interaction point 35 | - Customisable FAQ text 36 | - Detect the users language and display it as an emoji 37 | 38 | > 💡 Got a feature idea? Open an [issue](https://github.com/fabston/Telegram-Support-Bot/issues/new?assignees=&labels=enhancement&template=feature-request---.md) and I might implement it. 39 | 40 | ### Staff commands 41 | | Command | Description | 42 | | --- | --- | 43 | | /ban | Ban user by ID or reply | 44 | | /unban | Un-ban user by ID or reply | 45 | | /banned | List banned users | 46 | | /tickets or /t | List open tickets | 47 | | /close or /c | Manually close a ticket by reply | 48 | 49 | ### User commands 50 | | Command | Description | 51 | | --- | --- | 52 | | /start | Starts the bot | 53 | | /faq | Show the FAQ's | 54 | 55 | 56 | ## Installation 57 | > ⚠️ Best to run the bot on a VPS. I can recommend Hetzner's CX11 VPS for 2.89€/month. [Sign up](https://hetzner.cloud/?ref=tQ1NdT8zbfNY) now and receive **€20 free** credits. 58 | 1. Log into MySQL (`sudo mysql`) and create a dedicated database and user with the following commands: 59 | 1. `CREATE DATABASE TelegramSupportBot;` 60 | 1. `CREATE USER 'SupportBotUser'@'localhost' IDENTIFIED BY '';` 61 | 1. `GRANT ALL PRIVILEGES ON TelegramSupportBot . * TO 'SupportBotUser'@'localhost';` 62 | 1. `exit;` 63 | 1. Clone this repository `git clone https://github.com/fabston/Telegram-Support-Bot.git` 64 | 1. Create your virtual environment `python3 -m venv Telegram-Support-Bot` 65 | 1. Activate it `source Telegram-Support-Bot/bin/activate && cd Telegram-Support-Bot` 66 | 1. Install all requirements `pip install -r requirements.txt` 67 | 1. Edit and update [`config.py`](https://github.com/fabston/Telegram-Support-Bot/blob/master/config.py) 68 | 1. Run the bot `python main.py` 69 | 70 | 71 | ## Images 72 | ![Telegram Support Bot](https://raw.githubusercontent.com/fabston/Telegram-Support-Bot/master/assets/about.jpg) 73 | 74 | ## How can I help? 75 | All kinds of contributions are welcome 🙌! The most basic way to show your support is to `⭐️ star` the project, or raise [`🐞 issues`](https://github.com/fabston/Telegram-Support-Bot/issues/new/choose). 76 | 77 | *** 78 | 79 |

80 | Buy Me A Coffee 81 |

-------------------------------------------------------------------------------- /assets/about.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabston/Telegram-Support-Bot/1e46e01bd75198aa28960c345efa76487c11b27a/assets/about.jpg -------------------------------------------------------------------------------- /assets/bmac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabston/Telegram-Support-Bot/1e46e01bd75198aa28960c345efa76487c11b27a/assets/bmac.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabston/Telegram-Support-Bot/1e46e01bd75198aa28960c345efa76487c11b27a/assets/logo.png -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # --------------------------------------------- # 2 | # Plugin Name : Telegram Support Bot # 3 | # Author Name : fabston # 4 | # File Name : config.py # 5 | # --------------------------------------------- # 6 | 7 | # Telegram 8 | token = '' # More: https://core.telegram.org/bots#3-how-do-i-create-a-bot 9 | 10 | # MySQL Database 11 | mysql_host = 'localhost' 12 | mysql_db = 'TelegramSupportBot' 13 | mysql_user = 'SupportBotUser' 14 | mysql_pw = '' 15 | 16 | # Support Chat (Chat ID) 17 | support_chat = # Example: -1001429781350 | To find out your channels ID use: https://t.me/getidsbot 18 | 19 | # Misc 20 | time_zone = 'GMT+2' # Supports time zone 21 | bad_words_toggle = True # Enable / disable bad words filter 22 | spam_toggle = True # Enable / disable spam filter 23 | spam_protection = 5 # How many consecutive messages can be sent without a reply from the team 24 | open_ticket_emoji = 24 # After X amount of HOURS an emoji will pop up at /tickets 25 | 26 | # Messages 27 | text_messages = { 28 | 'start': 'Hi {}, how can we help you today?', 29 | 'faqs': 'Your FAQ text goes in here.', 30 | 'support_response': 'From: {}' # Support response is being added automatically. {} = refers to the staffs first name. 31 | } 32 | 33 | # Regex (https://regex101.com/) 34 | regex_filter = { 35 | 'bad_words': r'(?i)^(.*?(\b\w*fuck|shut up|dick|bitch|bastart|cunt|bollocks|bugger|rubbish|wanker|twat|suck|ass|pussy|arsch\w*\b)[^$]*)$' 36 | } 37 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # --------------------------------------------- # 2 | # Plugin Name : Telegram Support Bot # 3 | # Author Name : fabston # 4 | # File Name : main.py # 5 | # --------------------------------------------- # 6 | 7 | import config 8 | from resources import mysql_handler as mysql 9 | from resources import markups_handler as markup 10 | from resources import msg_handler as msg 11 | 12 | import telebot 13 | from datetime import datetime 14 | import arrow 15 | 16 | bot = telebot.TeleBot(config.token) 17 | 18 | mysql.createTables 19 | 20 | 21 | # Callback Handlers 22 | @bot.callback_query_handler(func=lambda call: True) 23 | def callback_inline(call): 24 | if call.message: 25 | if call.data == "faqCallbackdata": 26 | bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, 27 | text=config.text_messages['faqs'], parse_mode='Markdown', 28 | disable_web_page_preview=True) 29 | 30 | 31 | # Start Command 32 | @bot.message_handler(commands=['start']) 33 | def start(message): 34 | if message.chat.type == 'private': 35 | bot.send_message(message.chat.id, 36 | config.text_messages['start'].format(message.from_user.first_name) + msg.repo(), 37 | parse_mode='Markdown', disable_web_page_preview=True, reply_markup=markup.faqButton()) 38 | mysql.start_bot(message.chat.id) 39 | else: 40 | bot.reply_to(message, 'Please send me a PM if you\'d like to talk to the Support Team.') 41 | 42 | 43 | # FAQ Command 44 | @bot.message_handler(commands=['faq']) 45 | def start(message): 46 | if message.chat.type == 'private': 47 | bot.reply_to(message, config.text_messages['faqs'], parse_mode='Markdown', disable_web_page_preview=True) 48 | else: 49 | pass 50 | 51 | 52 | # Get All Open Tickets 53 | @bot.message_handler(commands=['tickets', 't']) 54 | def ot_handler(message): 55 | if message.chat.id == config.support_chat: 56 | if not mysql.open_tickets: 57 | bot.reply_to(message, "ℹ️ Great job, you answered all your tickets!") 58 | return 59 | 60 | ot_msg = '📨 *Open tickets:*\n\n' 61 | for user in mysql.open_tickets: 62 | bot.send_chat_action(message.chat.id, 'typing') 63 | ot_link = mysql.user_tables(int(user))['open_ticket_link'] 64 | 65 | now = arrow.now() 66 | diff = datetime.now() - mysql.user_tables(int(user))['open_ticket_time'] 67 | diff.total_seconds() / 3600 # seconds to hour 68 | time_since_secs = float(diff.seconds) 69 | time_since = now.shift(seconds=-time_since_secs).humanize() 70 | 71 | # Bring attention to 1 day old tickets 72 | if time_since_secs > config.open_ticket_emoji * 3600: 73 | alert = ' ↳ ⚠️ ' 74 | else: 75 | alert = ' ↳ ' 76 | 77 | ot_msg += "• [{0}{1}](tg://user?id={2}) (`{2}`)\n{5}_{3}_ [➜ Go to msg]({4})\n".format( 78 | bot.get_chat(int(user)).first_name, 79 | ' {0}'.format(bot.get_chat(int(user)).last_name) if bot.get_chat(int(user)).last_name else '', 80 | int(user), time_since, ot_link, alert) 81 | 82 | bot.send_message(message.chat.id, ot_msg, parse_mode='Markdown') 83 | else: 84 | pass 85 | 86 | 87 | # Close a ticket manually 88 | @bot.message_handler(commands=['close', 'c']) 89 | def ot_handler(message): 90 | if message.chat.id == config.support_chat: 91 | if message.reply_to_message and '(#id' in message.reply_to_message.text: 92 | bot.send_chat_action(message.chat.id, 'typing') 93 | user_id = int(message.reply_to_message.text.split('(#id')[1].split(')')[0]) 94 | ticket_status = mysql.user_tables(user_id)['open_ticket'] 95 | 96 | if ticket_status == 0: 97 | bot.reply_to(message, '❌ That user has no open ticket...') 98 | else: 99 | # Reset Open Tickets as well as the Spamfilter 100 | mysql.reset_open_ticket(user_id) 101 | bot.reply_to(message, '✅ Ok, closed that users ticket!') 102 | else: 103 | bot.reply_to(message, 'ℹ️ You\'d have to reply to a message') 104 | else: 105 | pass 106 | 107 | 108 | # Get Banned User 109 | @bot.message_handler(commands=['banned']) 110 | def ot_handler(message): 111 | if message.chat.id == config.support_chat: 112 | if not mysql.banned: 113 | bot.reply_to(message, "ℹ️ Great news, nobody got banned... Yet.") 114 | return 115 | 116 | ot_msg = '⛔️ *Banned users:*\n\n' 117 | for user in mysql.banned: 118 | bot.send_chat_action(message.chat.id, 'typing') 119 | ot_link = mysql.user_tables(int(user))['open_ticket_link'] 120 | 121 | ot_msg += "• [{0}{1}](tg://user?id={2}) (`{2}`)\n[➜ Go to last msg]({3})\n".format( 122 | bot.get_chat(int(user)).first_name, 123 | ' {0}'.format(bot.get_chat(int(user)).last_name) if bot.get_chat(int(user)).last_name else '', 124 | int(user), ot_link) 125 | 126 | bot.send_message(message.chat.id, ot_msg, parse_mode='Markdown') 127 | else: 128 | pass 129 | 130 | 131 | # Ban User 132 | @bot.message_handler(commands=['ban']) 133 | def ot_handler(message): 134 | try: 135 | if message.chat.id == config.support_chat: 136 | if message.reply_to_message and '(#id' in msg.msgCheck(message): 137 | user_id = msg.getUserID(message) 138 | banned_status = mysql.user_tables(user_id)['banned'] 139 | 140 | if banned_status == 1: 141 | bot.reply_to(message, '❌ That user is already banned...') 142 | else: 143 | mysql.ban_user(user_id) 144 | try: 145 | # Reset Open Tickets as well as the Spamfilter 146 | mysql.reset_open_ticket(user_id) 147 | except Exception as e: 148 | pass 149 | bot.reply_to(message, '✅ Ok, banned that user!') 150 | 151 | elif msg.getReferrer(message.text): 152 | user_id = int(msg.getReferrer(message.text)) 153 | banned_status = mysql.user_tables(user_id)['banned'] 154 | 155 | if banned_status == 1: 156 | bot.reply_to(message, '❌ That user is already banned...') 157 | else: 158 | mysql.ban_user(user_id) 159 | try: 160 | # Reset Open Tickets as well as the Spamfilter 161 | mysql.reset_open_ticket(user_id) 162 | except Exception as e: 163 | pass 164 | bot.reply_to(message, '✅ Ok, banned that user!') 165 | else: 166 | bot.reply_to(message, 'ℹ️ You\'d have to either reply to a message or mention an `Users ID`.', 167 | parse_mode='Markdown') 168 | except TypeError: 169 | bot.reply_to(message, '❌ Are you sure I interacted with that user before...?') 170 | 171 | 172 | # Un-ban Useer 173 | @bot.message_handler(commands=['unban']) 174 | def ot_handler(message): 175 | try: 176 | if message.chat.id == config.support_chat: 177 | if message.reply_to_message and '(#id' in msg.msgCheck(message): 178 | user_id = msg.getUserID(message) 179 | banned_status = mysql.user_tables(user_id)['banned'] 180 | 181 | if banned_status == 0: 182 | bot.reply_to(message, '❌ That user is already un-banned...') 183 | else: 184 | mysql.unban_user(user_id) 185 | bot.reply_to(message, '✅ Ok, un-banned that user!') 186 | 187 | elif msg.getReferrer(message.text): 188 | user_id = int(msg.getReferrer(message.text)) 189 | banned_status = mysql.user_tables(user_id)['banned'] 190 | 191 | if banned_status == 0: 192 | bot.reply_to(message, '❌ That user is already un-banned...') 193 | else: 194 | mysql.unban_user(user_id) 195 | bot.reply_to(message, '✅ Ok, un-banned that user!') 196 | else: 197 | bot.reply_to(message, 'ℹ️ You\'d have to either reply to a message or mention an `Users ID`.', 198 | parse_mode='Markdown') 199 | except TypeError: 200 | bot.reply_to(message, '❌ Are you sure I interacted with that user before...?') 201 | 202 | 203 | # Message Forward Handler (User - Support) 204 | @bot.message_handler(func=lambda message: message.chat.type == 'private', content_types=['text', 'photo', 'document']) 205 | def echo_all(message): 206 | while True: 207 | mysql.start_bot(message.chat.id) 208 | user_id = message.chat.id 209 | message = message 210 | banned = mysql.user_tables(user_id)['banned'] 211 | ticket_status = mysql.user_tables(user_id)['open_ticket'] 212 | ticket_spam = mysql.user_tables(user_id)['open_ticket_spam'] 213 | 214 | if banned == 1: 215 | return 216 | elif msg.spam_handler_warning(bot, user_id, message): # First spam warning 217 | return 218 | elif msg.bad_words_handler(bot, message): 219 | return 220 | elif msg.spam_handler_blocked(bot, user_id, message): # Final spam warning // user cant send messages anymore 221 | return 222 | elif ticket_status == 0: 223 | mysql.open_ticket(user_id) 224 | continue 225 | else: 226 | msg.fwd_handler(user_id, bot, message) 227 | return 228 | 229 | 230 | # Message Forward Handler (Support - User) 231 | @bot.message_handler(content_types=['text', 'photo', 'document']) 232 | def echo_all(message): 233 | while True: 234 | try: 235 | try: 236 | user_id = msg.getUserID(message) 237 | message = message 238 | text = message.text 239 | msg_check = msg.msgCheck(message) 240 | ticket_status = mysql.user_tables(user_id)['open_ticket'] 241 | banned_status = mysql.user_tables(user_id)['banned'] 242 | 243 | if banned_status == 1: 244 | # If User is banned - un-ban user and sent message 245 | mysql.unban_user(user_id) 246 | bot.reply_to(message, 'ℹ️ *FYI: That user was banned.*\n_Un-banned and sent message!_', 247 | parse_mode='Markdown') 248 | 249 | elif ticket_status == 1: 250 | # Reset Open Tickets as well as the Spamfilter 251 | mysql.reset_open_ticket(user_id) 252 | continue 253 | 254 | else: 255 | if message.reply_to_message and '(#id' in msg_check: 256 | msg.snd_handler(user_id, bot, message, text) 257 | return 258 | 259 | except telebot.apihelper.ApiException: 260 | bot.reply_to(message, '❌ I was unable to send that message...\nThe user might\'ve blocked me.') 261 | return 262 | 263 | except Exception as e: 264 | bot.reply_to(message, '❌ Invalid command!') 265 | return 266 | 267 | 268 | print("Telegram Support Bot started...") 269 | bot.polling() 270 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyTelegramBotAPI 2 | pymysql 3 | arrow 4 | cryptography -------------------------------------------------------------------------------- /resources/lang_emojis.py: -------------------------------------------------------------------------------- 1 | # --------------------------------------------- # 2 | # Plugin Name : Telegram Support Bot # 3 | # Author Name : fabston # 4 | # File Name : lang_emojis.py # 5 | # --------------------------------------------- # 6 | 7 | def lang_emoji(lang): 8 | if lang == 'en': 9 | emoji = '🇺🇸' 10 | elif lang == 'de': 11 | emoji = '🇩🇪' 12 | elif lang == 'es': 13 | emoji = '🇪🇸' 14 | elif lang == 'se': 15 | emoji = '🇸🇪' 16 | elif lang == 'no': 17 | emoji = '🇳🇴' 18 | elif lang == 'ru': 19 | emoji = '🇷🇺' 20 | elif lang == 'ua': 21 | emoji = '🇺🇦' 22 | elif lang == 'it': 23 | emoji = '🇮🇹' 24 | elif lang == 'nz': 25 | emoji = '🇳🇿' 26 | elif lang == 'nl': 27 | emoji = '🇳🇱' 28 | elif lang == 'mx': 29 | emoji = '🇲🇽' 30 | elif lang == 'pt': 31 | emoji = '🇵🇹' 32 | elif lang == 'br': 33 | emoji = '🇧🇷' 34 | elif lang == 'au': 35 | emoji = '🇦🇺' 36 | elif lang == 'ca': 37 | emoji = '🇨🇦' 38 | elif lang == 'cr': 39 | emoji = '🇨🇷' 40 | elif lang == 'dk': 41 | emoji = '🇩🇰' 42 | elif lang == 'ie': 43 | emoji = '🇮🇪' 44 | elif lang == 'is': 45 | emoji = '🇮🇸' 46 | elif lang == 'th': 47 | emoji = '🇹🇭' 48 | elif lang == 'fr': 49 | emoji = '🇫🇷' 50 | elif lang == 'gr': 51 | emoji = '🇬🇷' 52 | elif lang == 'pl': 53 | emoji = '🇵🇱' 54 | elif lang == 'fi': 55 | emoji = '🇫🇮' 56 | elif lang == 'hk': 57 | emoji = '🇭🇰' 58 | elif lang == 'ar': 59 | emoji = '🇦🇷' 60 | elif lang == 'tr': 61 | emoji = '🇹🇷' 62 | elif lang == 'kr': 63 | emoji = '🇰🇷' 64 | elif lang == 'jp': 65 | emoji = '🇯🇵' 66 | elif lang == 'cn': 67 | emoji = '🇨🇳' 68 | elif lang == 'in': 69 | emoji = '🇮🇳' 70 | else: 71 | emoji = lang 72 | 73 | return emoji -------------------------------------------------------------------------------- /resources/markups_handler.py: -------------------------------------------------------------------------------- 1 | # --------------------------------------------- # 2 | # Plugin Name : Telegram Support Bot # 3 | # Author Name : fabston # 4 | # File Name : markups_handler.py # 5 | # --------------------------------------------- # 6 | 7 | import config 8 | from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton 9 | 10 | 11 | def faqButton(): 12 | markup = InlineKeyboardMarkup() 13 | markup.add(InlineKeyboardButton('Read our FAQ\'s', callback_data='faqCallbackdata')) 14 | return markup 15 | -------------------------------------------------------------------------------- /resources/msg_handler.py: -------------------------------------------------------------------------------- 1 | # --------------------------------------------- # 2 | # Plugin Name : Telegram Support Bot # 3 | # Author Name : fabston # 4 | # File Name : msg_handler.py # 5 | # --------------------------------------------- # 6 | 7 | import config 8 | from resources import mysql_handler as mysql 9 | from resources import lang_emojis as emoji 10 | import re 11 | import datetime 12 | import time 13 | import arrow 14 | 15 | 16 | def getReferrer(text): 17 | return text.split()[1] if len(text.split()) > 1 else None 18 | 19 | 20 | def msg_type(message): 21 | if message.content_type == 'text': 22 | msg_type = message.text 23 | elif message.content_type == 'photo' or message.content_type == 'document': 24 | msg_type = message.caption 25 | return msg_type 26 | 27 | 28 | def getUserID(message): 29 | if message.reply_to_message.content_type == 'text': 30 | user_id = int(message.reply_to_message.text.split('(#id')[1].split(')')[0]) 31 | elif message.reply_to_message.content_type == 'photo' or message.reply_to_message.content_type == 'document': 32 | user_id = int(message.reply_to_message.caption.split('(#id')[1].split(')')[0]) 33 | return user_id 34 | 35 | 36 | def msgCheck(message): 37 | if message.reply_to_message.content_type == 'text': 38 | msg_check = message.reply_to_message.text 39 | elif message.reply_to_message.content_type == 'photo' or message.reply_to_message.content_type == 'document': 40 | msg_check = message.reply_to_message.caption 41 | return msg_check 42 | 43 | 44 | def msgCaption(message): 45 | if message.caption == None: 46 | msgCaption = '' 47 | else: 48 | msgCaption = message.caption 49 | return msgCaption 50 | 51 | 52 | # (Support - User Handler) 53 | def snd_handler(user_id, bot, message, txt): 54 | try: 55 | if message.content_type == 'text': 56 | bot.send_chat_action(user_id, 'typing') 57 | bot.send_message(user_id, \ 58 | config.text_messages['support_response'].format( 59 | bot.get_chat(user_id).first_name) + f'\n\n{message.text}', parse_mode='Markdown', 60 | disable_web_page_preview=True) 61 | 62 | elif message.content_type == 'photo': 63 | bot.send_chat_action(user_id, 'upload_photo') 64 | bot.send_photo(user_id, message.photo[-1].file_id, 65 | caption=config.text_messages['support_response'].format( 66 | bot.get_chat(user_id).first_name) + f'\n\n{msgCaption(message)}', 67 | parse_mode='Markdown') 68 | 69 | elif message.content_type == 'document': 70 | bot.send_chat_action(user_id, 'upload_document') 71 | bot.send_document(user_id, message.document.file_id, 72 | caption=config.text_messages['support_response'].format( 73 | bot.get_chat(user_id).first_name) + f'\n\n{msgCaption(message)}', 74 | parse_mode='Markdown') 75 | else: 76 | pass 77 | 78 | except Exception as e: 79 | print(e) 80 | bot.reply_to(message, '❌ That format is not supported.') 81 | return 82 | 83 | 84 | # (User - Support Handler) 85 | def fwd_handler(user_id, bot, message): 86 | # Update the Spamfilter 87 | mysql.spam(message.chat.id) 88 | lang_emoji = emoji.lang_emoji(message.from_user.language_code) 89 | 90 | if message.content_type == 'text': 91 | msg = bot.send_message(config.support_chat, "[{0}{1}](tg://user?id={2}) (#id{2}) | {3}\n\n{4}".format( 92 | message.from_user.first_name, 93 | ' {0}'.format(message.from_user.last_name) if message.from_user.last_name else '', 94 | message.from_user.id, lang_emoji, message.text), parse_mode='Markdown', disable_web_page_preview=True) 95 | 96 | elif message.content_type == 'photo': 97 | msg = bot.send_photo(config.support_chat, message.photo[-1].file_id, 98 | caption="[{0}{1}](tg://user?id={2}) (#id{2}) | {3}\n\n{4}".format( 99 | message.from_user.first_name, 100 | ' {0}'.format(message.from_user.last_name) if message.from_user.last_name else '', 101 | message.from_user.id, lang_emoji, msgCaption(message)), parse_mode='Markdown') 102 | 103 | elif message.content_type == 'document': 104 | msg = bot.send_document(config.support_chat, message.document.file_id, 105 | caption="[{0}{1}](tg://user?id={2}) (#id{2}) | {3}\n\n{4}".format( 106 | message.from_user.first_name, 107 | ' {0}'.format(message.from_user.last_name) if message.from_user.last_name else '', 108 | message.from_user.id, lang_emoji, msgCaption(message)), parse_mode='Markdown') 109 | 110 | elif message.content_type == 'sticker': 111 | msg = bot.send_sticker(user_id, message.sticker.file_id) 112 | 113 | else: 114 | bot.reply_to(message, '❌ That format is not supported and won\'t be forwarded.') 115 | 116 | channel_id = re.sub(r"-100(\S+)", r"\1", str(config.support_chat)) 117 | message_id = msg.message_id 118 | message_link = f'https://t.me/c/{channel_id}/{message_id}' 119 | mysql.post_open_ticket(message_link, user_id) 120 | 121 | return fwd_handler 122 | 123 | 124 | def bad_words_handler(bot, message): 125 | if config.bad_words_toggle: 126 | try: 127 | if re.findall(config.regex_filter['bad_words'], msg_type(message)): 128 | bot.reply_to(message, '❗️ Watch your tongue...') 129 | return bad_words_handler 130 | except Exception as e: 131 | pass 132 | 133 | 134 | def time_zone(): 135 | current_time = arrow.now(config.time_zone).strftime('%I:%M %p') 136 | return current_time 137 | 138 | 139 | def repo(): 140 | msg = '\n\n[» Source Code](github.com/vsnz/Telegram-Support-Bot)' 141 | return msg 142 | 143 | 144 | def spam_handler_warning(bot, user_id, message): 145 | if config.spam_toggle: 146 | ticket_spam = mysql.user_tables(user_id)['open_ticket_spam'] 147 | if ticket_spam > config.spam_protection: 148 | bot.reply_to(message, 149 | '{}, your messages are not being forwarded anymore. Please wait until the team responded. Thank you.\n\n' \ 150 | f'_The support\'s local time is_ `{current_time}`.'.format(message.from_user.first_name), 151 | parse_mode='Markdown') 152 | return spam_handler_warning 153 | 154 | 155 | def spam_handler_blocked(bot, user_id, message): 156 | if config.spam_toggle: 157 | ticket_spam = mysql.user_tables(user_id)['open_ticket_spam'] 158 | if ticket_spam == config.spam_protection - 1: 159 | fwd_handler(user_id, bot, message) 160 | bot.reply_to(message, 161 | 'We will be with you shortly.\n\n{}, to prevent spam you can only send us *1* more message.\n\n' \ 162 | f'_The support\'s local time is_ `{current_time}`.'.format(message.from_user.first_name), 163 | parse_mode='Markdown') 164 | return spam_handler_blocked 165 | 166 | 167 | current_time = time_zone() 168 | -------------------------------------------------------------------------------- /resources/mysql_handler.py: -------------------------------------------------------------------------------- 1 | # --------------------------------------------- # 2 | # Plugin Name : Telegram Support Bot # 3 | # Author Name : fabston # 4 | # File Name : mysql_handler.py # 5 | # --------------------------------------------- # 6 | 7 | import pymysql 8 | import config 9 | from datetime import datetime 10 | 11 | 12 | # Connect to MySQL Database 13 | def getConnection(): 14 | connection = pymysql.connect(host=config.mysql_host, 15 | user=config.mysql_user, 16 | password=config.mysql_pw, 17 | db=config.mysql_db, 18 | charset='utf8mb4', 19 | cursorclass=pymysql.cursors.DictCursor, 20 | autocommit=True) 21 | return connection 22 | 23 | 24 | def createTables(): 25 | connection = getConnection() 26 | with connection.cursor() as cursor: 27 | tablename = "users" 28 | try: 29 | cursor.execute( 30 | " CREATE TABLE `" + tablename + "` ( `userid` int(11) DEFAULT NULL, `open_ticket` int(4) DEFAULT 0, `banned` int(4) DEFAULT 0, \ 31 | `open_ticket_spam` int(4) DEFAULT 1, `open_ticket_link` varchar(50) DEFAULT NULL, `open_ticket_time` datetime NOT NULL DEFAULT '1000-01-01 00:00:00')") 32 | return createTables 33 | except Exception as e: 34 | print(e) 35 | 36 | 37 | def spam(user_id): 38 | connection = getConnection() 39 | with connection.cursor() as cursor: 40 | sql = "SELECT banned, open_ticket, open_ticket_spam FROM users WHERE userid = %s" 41 | cursor.execute(sql, user_id) 42 | data = cursor.fetchone() 43 | ticket_spam = data['open_ticket_spam'] 44 | 45 | sql = "UPDATE users SET open_ticket_spam = %s WHERE userid = %s" 46 | spam = ticket_spam + 1 47 | cursor.execute(sql, (spam, user_id)) 48 | return spam 49 | 50 | 51 | def user_tables(user_id): 52 | connection = getConnection() 53 | with connection.cursor() as cursor: 54 | sql = "SELECT open_ticket, banned, open_ticket_time, open_ticket_spam, open_ticket_link FROM users WHERE userid = %s" 55 | cursor.execute(sql, user_id) 56 | data = cursor.fetchone() 57 | return data 58 | 59 | 60 | def getOpenTickets(): 61 | connection = getConnection() 62 | with connection.cursor() as cursor: 63 | tmp = [] 64 | cursor.execute("SELECT userid FROM users WHERE open_ticket = 1") 65 | for i in cursor.fetchall(): 66 | tmp.append(i['userid']) 67 | return tmp 68 | 69 | 70 | def getBanned(): 71 | connection = getConnection() 72 | with connection.cursor() as cursor: 73 | tmp = [] 74 | cursor.execute("SELECT userid FROM users WHERE banned = 1") 75 | for i in cursor.fetchall(): 76 | tmp.append(i['userid']) 77 | return tmp 78 | 79 | 80 | def start_bot(user_id): 81 | connection = getConnection() 82 | with connection.cursor() as cursor: 83 | sql = "SELECT EXISTS(SELECT userid FROM users WHERE userid = %s)" 84 | cursor.execute(sql, user_id) 85 | result = cursor.fetchone() 86 | # If the User never started the bot before, add the Telegram ID to the database 87 | if not list(result.values())[0]: 88 | sql = "INSERT INTO users(userid) VALUES (%s)" 89 | cursor.execute(sql, user_id) 90 | 91 | 92 | def open_ticket(user_id): 93 | connection = getConnection() 94 | with connection.cursor() as cursor: 95 | sql = "UPDATE users SET open_ticket = 1, open_ticket_time = %s WHERE userid = %s" 96 | now = datetime.now() 97 | cursor.execute(sql, (now, user_id)) 98 | open_tickets.append(user_id) 99 | return open_ticket 100 | 101 | 102 | def post_open_ticket(link, msg_id): 103 | connection = getConnection() 104 | with connection.cursor() as cursor: 105 | sql = "UPDATE users SET open_ticket_link = %s WHERE userid = %s" 106 | cursor.execute(sql, (link, msg_id)) 107 | return post_open_ticket 108 | 109 | 110 | def reset_open_ticket(user_id): 111 | connection = getConnection() 112 | with connection.cursor() as cursor: 113 | sql = "UPDATE users SET open_ticket = 0, open_ticket_spam = 1 WHERE userid = %s" 114 | cursor.execute(sql, user_id) 115 | open_tickets.pop(open_tickets.index(user_id)) 116 | return reset_open_ticket 117 | 118 | 119 | def ban_user(user_id): 120 | connection = getConnection() 121 | with connection.cursor() as cursor: 122 | sql = "UPDATE users SET banned = 1 WHERE userid = %s" 123 | cursor.execute(sql, user_id) 124 | banned.append(user_id) 125 | return ban_user 126 | 127 | 128 | def unban_user(user_id): 129 | connection = getConnection() 130 | with connection.cursor() as cursor: 131 | sql = "UPDATE users SET banned = 0 WHERE userid = %s" 132 | cursor.execute(sql, user_id) 133 | banned.pop(banned.index(user_id)) 134 | return unban_user 135 | 136 | 137 | createTables = createTables() 138 | open_tickets = getOpenTickets() 139 | banned = getBanned() 140 | --------------------------------------------------------------------------------