A simple telegram bot.
Which main feature is its sed command, the famous UNIX command.
The bot has the following dependencies:
requeststypes-requestspillowhtml2textyt-dlpmatplotlibsympy
Which can be installed with:
pip3 install -r requirements.txt \
-r dev-requirements.txt # Add this when developing the bot
poetry shell
The captcha command may depend on fonts-freefont-ttf, which can be installed
via:
sudo apt install fonts-freefont-ttfThe OCR command depends on tesseract-ocr, which can be installed with:
sudo apt install tesseract-ocrYou have to place your bot token either in the environment variables or in the config files, by manually editing them:
nano sadbot/config.py
nano Dockerfile # If you are using Docker/Podman
In the sadbot/config.py file you can also find the settings for configurable
bot commands (like cringe, roulette etc.)
Here's how you run the bot manually:
nohup PYTHONPATH=. python3 -m sadbot & disown
Alternatively, you can create a new systemd service, which handles the bot restart in a way more neat way, with these commands:
sed -i 's/userplaceholder/BOTUSER' sadbot.service
sed -i 's/pathplaceholder/BOTPATH' sadbot.service
sudo cp sadbot.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo service sadbot startAnd to check the bot status you can simply type:
sudo service sadbot statusor, for reading the full log:
sudo journalctl -u sadbot.serviceIf you like docker or podman, you can easily build the container using the Dockerfile:
sudo docker build -m sadbot .Then you can easily start the bot with:
sudo docker run -it sadbotPull requests are welcome.
It's recommended to use pylint, black and mypy to check and review the
code before submitting it. You can set up all these three by enabling
pre-commit:
pre-commit installIf you want to add a new command you just have to write a new module file in the
commands directory.
Here is a sample bot command, sample_command.py, it's code is pretty
self-explanatory:
"""Sample/uwuspeak bot command"""
# this import is required in every module:
from typing import Optional, List
# this imports is optional:
import re
# this imports is optional:
from sadbot.message_repository import MessageRepository
# you need to import the handler type, every command is tied to just one type
from sadbot.command_interface import CommandInterface, BOT_HANDLER_TYPE_MESSAGE
from sadbot.message import Message
# then you need to import the bot action type
from sadbot.bot_action import BotAction, BOT_ACTION_TYPE_REPLY_IMAGE
# the class name must be the pascal case of the module filename + "BotCommand"
class UwuBotCommand(CommandInterface):
"""This is the sample command bot command class"""
# the constructor is NOT required. Anyway if the bot command need some
# dependencies, they will be automatically injected through it
def __init__(self, message_repository: MessageRepository):
"""Initializes the command class"""
self.message_repository = message_repository
@property
def handler_type(self) -> int:
"""Here is the type of event handled by the command"""
return BOT_HANDLER_TYPE_MESSAGE
@property
def command_regex(self) -> str:
"""Here is the regex that triggers this bot command"""
regex = r"uwu(.*)?"
return regex
def get_reply(self, message: Optional[Message] = None) -> Optional[List[BotAction]]:
"""This function can return some bot actions/replies that will be sent later"""
# this is an example on how you can process the message that triggered
# the command to get a custom reply
# here we are getting the last message sent in the chat with the support of
# a very useful module of sadbot we're injecting into this class
# we could also have injected the direct database connection and retrieved
# the last message directly
if message is None:
return None
if message.reply_id is not None:
previous_message = self.message_repository.get_message_from_id(
message.reply_id, message.chat_id
)
else:
matching_message = Message(chat_id=message.chat_id)
previous_message = self.message_repository.get_previous_message(
matching_message, r"^(?!\s*$).+"
)
if previous_message is None:
return None
if previous_message.text is None or previous_message.text == "":
return None
try:
# uwu-mocking the message found
reply_text = re.sub(r"(\w{3})", r"\1w", previous_message.text)
except re.error:
return None
# here is how you open/set an image for the bot action, please note that in
# this project the standard directory for storing command assets is:
# ./sadbot/assets/{command_name}/
with open("./sadbot/assets/uwu/uwu.jpg", mode="rb") as reply_image_file:
reply_image = reply_image_file.read()
return [
BotAction(
BOT_ACTION_TYPE_REPLY_IMAGE,
reply_image=reply_image,
reply_text=reply_text,
)
]Managers are used for handling sent messages/actions and to perform actions
at specific moments.
Managers are called indirectly by the commands, through the BotAction
attribute reply_callback_manager_name.
Additional info may be passed by the commands to the managers by through the
BotAction attribute reply_callback_manager_info
So when a manager triggered, it's handle_callback function will be called and
it will be given as parameters:
- the message that triggered the command
- the optional outgoing message/reply that triggered the manager
- the optional callback info, which will be located in
message.text
During the bot startup, every manager is initialized. Managers may behave like containers for multiple sub-managers.
- Antiflood, samewords count and newlines count
- Flush completed TODOs (lol)
- VC Radio
- Group admin settings: enabled modules etc.
- Add user-requested assets to the commands
- Sympy plots: return the bytearray instead of writing it into a file
- Fix bookmark command (it says he found bookmarks even if it didn't)
- Fix mute - sometimes it doesn't work
- Fix ban - sometimes it doesn't work
- Fix kick - sometimes it doesn't work
- Fix seen command - sometimes it doesn't work. It may be a username case sensivity issue
- Flush command (deletes every message after the selected one) (dangerous)
- Create a function that returns {username} / {sender_name} / "User" so we don't repeat the same code in every command (mute, ban, kick, warn, etc.)
- Specific thread post retrival
- Bible json asset
- Move lists code into its own separate class?
- Virustotal command
- Translate voice command
- Slowmode command
- IMPORTANT Update all the files-handling code in order to make it work
with
BytesIOrather thanbytes - Good morning / Good night messages/gifs (see how .setrules work); we have to write a manager for this
- Update README.md (check it overall and complete the description for the bot managers)
- Meme caption adder command: adds some text to an image or a video
- Add warn reason (and clean old warnings please...)
- Restart command: better reply message: reply message on startup
- Username change detector (see the UPDATE query on the usernames table; it's halfway done: the table with the data is already there, we just need a callback to send the message)
- Plot3D animated videos
- Metadata command
-
Fix mute command short interval - Fix
BOT_ACTION_TYPE_NONEmanagers callback: they are dispatched only after a message is sent, therefore it fails do dispatch new managers -
Add new tables: for images, for edits and for usernamesUpdated existing table -
Del commanduseless - Fix rand command regex: add leading dot / exclamation mark
- Update go schizo regex: allow "goschizo" without space
-
Git pull command: better reply message - Fix uwu command: sometimes it doesn't reply, probably because it loads a previous empty message
- Compliment command: update regex in order to reply to "Thanks bot"
- Change captcha kick time in config.py to 5 minutes
- Fix translate command: it doesn't support newlines
- Update weed command with "cool 50 ways to say no to weed" (search it on google lmao)
- Fix activity output message (whitespace alignment)
- Deepfry image command
-
TranslateOCR command: imagestranslateOCR with Tesseract. - Git pull & restart command for the bot owner
- Report command
- Fix seen command
- Fix .remindme (it doesn't work when reminder time is very small)
- Check if the mute/unmute replies are correct (or if it doesn't handle the '@' before the usernames)
- .slap command
- Random hug gifs?
- .getchatid command
- Fix roulette command: initialize (and store) the instance outside the command, in sadbot/classes/revolver.py: the commands call that class and gets the response, period, all the "logic" should be in there. (Because commands are not persistent in memory)
- Fix roulette revolver regex
- .hug command
- Redefine the data directories to sadbot/data/command_name (no command should ever write outside of his data directory. So we have to just change the dir of the captcha image command, and of some other stuff)
- Fix .remindme
- Fix roulette command
- Check if the mute/unmute replies are correct
- Random hug gifs?
- Fix roulette revolver regex
- .getchatid command
- .slap command
- .hug command
- Redefine the data directories to sadbot/assets/command_name and the commands with assets
- Reminder tag/bookmark command
- Add media support for outgoing messages
- Fix the roulette code
- Seen command
- User ratelimit
- Group ratelimit
- FBI watchlist
- Captcha command
- Translate command
- Asynchronous processing <- HIGH PRIORITY | Multithreading
- Welcome messages
- Big chan url pictures
- Beaver command
- Stay cool on weed questions
- Multiple messages per command (return a list)
- Chat events handlers
- Mute command
- Ban command
- Kick command
- MyPy cleanup
- Rewrite managers
- BOT_ACTION_NONE