Telegram chat command execution framework based on telethon library
Basically this allows you to turn any(or all of them) telegram chat into REPL where you call a command and this app from under your account responds to you with the result.
You can call a commands predefined in your files with .
operator:
But it is much more than that! This projects provides:
- Built-in access permitions. Yes, you may allow/ban other users to call your commands. Just like in Linux.
- Built-in help on commands.
- Allows you to access any of Telegram API via Telethon library. Like invoke a command once someone edits a message. Or change image in your profile whenever someone else does this.
- Grants solid support for logging.
- Graceful and in-chat handling of errors.
- Serialization and deserialization of state on exit.
- Localization to popular languages.
- You may customize nearly EVERYTHING(yes, even syntax) via config.
Be careful, do not violate Telegram's Terms of service.
If you use the Telegram API for flooding, spamming, faking subscriber and view counters of channels, you will be banned forever. Due to excessive abuse of the Telegram API, all accounts that sign up or log in using unofficial Telegram API clients are automatically put under observation to avoid violations of the Terms of Service.
cp .env_exmpl .env
# fulfill .env with your data
# ...
# run exmpl(as a daemon)
docker-compose up iahr_exmpl
# run tests(optional)
docker-compose up iahr_tests
P. S. try adding sudo if something is faulty
Routine is your python function with some metadata that can be accessed from telegram
There are two type of routines:
- Commands - text-based routines, call it by typing it's name in message.
- Handlers - reactions to some events(e.g.
onedit
)
Senders decorators are used to register your routine. Currently there are three senders:
VoidSender
- your function returns nothingTextSender
- it returnsstr
MediaSender
- return file-like object
You can import it from iahr.reg
.
And easily create new senders(more on it later, below).
Every function, if not specified otherwise, takes event object from telethon as first parameter. This helps to provide custom behavior and enables more freedom in terms of an API.
You could easily add new commands, Iahr
was designed for it. And moreover you could easily compose them(like plain old functions in any modern PL), as long as one commands return type corresponds to other parameter's type.
For example:
.upper word
~> WORD
.lower wORd
~> word
and the most epic one(attention!):
.lower .upper word
~> word
- WOW, isn't it?
- It is!
And this is just text examples, you can use any data format to pass in/out of commands.
I'm feeling lonely and bored, so i want to be able to play my favorite game marcopolo with myself in any chat.
Just add new function, like you would do it in python.
def marco():
return 'polo'
And that's wrong!. Stop, not so fast. It lacks three more things:
Iahr
doesn't know anything about your code. So it should be registered.- Use
async def
because it's faster - You need more info to reply to a message, just an additional parameter:
event
So now:
@VoidSender()
async def marco(event):
await event.message.reply('polo')
That's it. It automatically adds your command to the list of commands on .commands
.
But there is more than one way to do it right:
from telethon.events import NewMessage as newmsg
@TextSender(
name='marco', # how you will call it in chat
about='An ancient game in modern messenger', # description on `.help marco`
take_event=False, # whether it takes event, default - True
multiret=False, # whether it returns one value, or multiple, default - False
on_event=newmsg, # what event it should be called on, default - events.NewMessage
tags={'games'}) # tags facility to quickly search for commands
async def marco():
return 'polo'
NB: don't forget multiret=True
when you want to return list of values instead of one value: list.
Well that's now a lot more, it's just an overview of an API and you don't need to use it whole. Personally I would stop on something like this:
@TextSender(take_event=False, about="""
An ancient game in modern messenger
""")
async def marco():
return 'polo'
# or
@TextSender(about='An ancient game in modern messenger')
async def marco(_):
return 'polo'
I want to trigger if someone edits a message. Let's do this.
By the way, there are other types of events. So here is an example of how you could use it, currently only onedit
is supported. But it is implemented with extension in future.
from telthon import events
@TextSender(take_event=False, about="""
Reply, when someone edits the message
""", on_event=events.MessageEdited)
async def isaw():
return 'I saw what you did here! You bastard!'
To run a command both user and chat need to be allowed to run this command(except if it's you(admin) who is running the command).
help
Info to start with
synhelp
Info about syntax rules and some usage examples
acceshelp
Get help about access rights actions
commands
Get the list of all commands or info about command
Commands list:
`.commands`
Info about command:
`.commands commands`
handlers
Get the info about handlers.
Handler is a reaction to some event:
Handlers list(divided by event types):
`.handlers`
Handlers on specific event type:
`.handlers onedit`
Info about handlers(specify event type):
`.handlers onedit [hndl1 hndl2]`
tags
Get the list of all tags or list of commands tagged,
Tags list:
`.tags`
Commands tagged with `default`:
`.tags default`
{allow|ban|allowed}{usr|chat}
You can customize access level
to all your routines anytime.
------------------------------------
1. First you need to decide which
command to use, start typing '.'
2. Then select the access action
write one of three: 'ban', 'allow' or
'allowed'(to find out the rights)
3. Then continue and select one
of two: 'chat' or 'usr' entities
For example: .allowedusr
Continue typing, tell what you need...
------------------------------------
Whatever command you've chosen in
the previous paragraph, the interface
to it is all the same.
It depends on type of routine,
some examples:
.allowchat commands [chat1 chat2] [synhelp help]
Or deduce entity from context,
by using $ wildcard.
USR: usr you are replying to, or you
CHAT: current chat
.allowedusr handlers $ onedit somehandler
Apply to all commands, handlers, tags:
.banusr commands $
.allowedchat handlers onedit $
.banchat tags $
Use tags to access whole categories
of commands/handlers:
.allowchat tags $ r[default admin]r
errignore
Ignore a chat when processing commands from
banned users. Reduces spam level
by chatname or id all commands:
`.errignore chatname`
all chats:
`.errignore *`
the chat that you are writing this in:
`.errignore`
errverbose
Enable chat when processing commands from
banned users. Increases spam level, but
also increases clarity
by chatname or id all commands:
`.errverbose chatname`
all chats:
`.errverbose *`
the chat that you are writing this in:
`.errverbose`
tagall
Tag all participants in a chat
openonline
Open the file in online service
(Google Docs, Sheets and Slides currently supported)
audiocrop
Crop an audio track.
Example:
.audiocrop 2s 5s
the same
.audiocrop 2000 5000
audioreverse
Reverse an audio track
audiodistort
Distort an audio track
Change .env
file to fit your needs.
NB: TG_SESSION_PATH
and IAHR_DATA_FOLDER
are relative to the working directory(exmpl
or tests
).
To configure use config.json
file relative to IAHR_DATA_FOLDER
env var.
(see example here)
Or a function config
from iahr.config
. The first variant is preferable,
because it initializes program before it's execution.
Go to the session file and modify it's json manually to get what you want.
It's path is ${IAHR_DATA_FOLDER}/iahr.session
.
No words, just action. For example, here are how MediaSender
defined.
from iahr.reg import create_sender, any_send, MultiArgs
# self.res - result of a function: MultiArgs object
# self.event - original event object
async def __media_send(self):
# MultiArgs contains only `args` - list of args
res = self.res.args[0]
await any_send(self.event, file=res)
MediaSender = create_sender('MediaSender', __media_send)
And that's it. You could register new functions with @MediaSender
.
Note: args and kwargs you pass to any_send
after event are all that you pass to event.message.reply.
Take a look on the AudioSender class.
If you have more demanding wishes. We are glad to satisfy them. Here is how to initialize it all manually:
from iahr import init
from iahr.run import Manager
from iahr.reg import Register
from iahr.config import IahrConfig
# ...
# init telethons `client` variable
# ...
app = Manager()
register = Register(client, app)
IahrConfig.init(app, register)
That's how iahr.init
works. But what if you want to create custom Manager and custom Register? We have something for you just overload ABCManager
and ABCRegister
. But i'll leave you on your own here. Go check how it works by example in Manager and Register.
Use yapf with default settings(pep8). Be sure to run yapf -ri *
before pushing. Or use an online demo.
Note: there are errors regarding new walrus operator and f-strings, just remove them manually and then add it back after formatting.
There are templates for issues and pull requests in here. Use them.
There are much more to learn in the wisdom of python's success. It is much easier to write code(commands in chat) when you have a lot of useful errors appearing when you do something wrong. So it's a major issue to work on. Some ideas on what errors we'd like to provide:
-
Incompatible commands error
When there are more or less args than command can take, we'll print
{command} takes more/less args, please check your query
There is such a small directory as commands. And you can fulfill it with whole bunch of other modules containing commands for different topics, like text, photos audiofiles, jokes and pranks, videos and many others, just use your imagination. That's the point!
Right now Iahr
supports english and russian languages.
You can provide localization to your own language.
For this you need to alter:
Iahr
internal messages: create file {yourlang}.py in iahr/localization.- default commands messages: create according dictionary in iahr/commands/default/localization.py.
Right now there are lots of unit tests. Endpoint is a full test coverage. We need to add tests to check if all is working as expected at telegram level. So here how integration testing could be done:
- Mock
- Create two clients and/or use test servers
- Script to generate
README.md
from code - Generate docs on commit
- Reformat code on commit
- Enable to easily run docker from vscode
- Add site that allows users to easily
- configure and create
iahr
telegram client from web gui - manage commands(for example access rights) from gui
- share command sets and include it to their own clients
- configure and create
- Rerun after code changes script
- Command that allows to execute regex
- Typing in chat for specific time
- Change
tagall
to create multiple messages so that it works - Simplify access rights commands interface
- Add commands to get access rights user and chat lists for specific command
- Command that lets you define global constants(text, media, people)
- Remove messages after specific time passed
- Text to voice
- Sticker quotes and it's search
- Overlay two audios
- Get user location on premise
-
Dynamic command creation(see branch alias)
something like
.alias r[oneword x: .concat .split $1]r
and then.oneword [nice nice day] => niceniceday
-
Autocompletion like in shell with help of some special character(e.g. '-')
-
Does tags enough? If no, you can always implement modules.
-
Save data in telegram, like a permanent storage(implement a db interface?)
-
Formalization of parsing algorithm with lex/yacc
- Document all config.json
- Mock telegram api integration tests
Iah
is egyptian god of the new moon. And in russian language "echo" and "moon" are homophones. The words that pronounce the same, but have different meaning. So "echo" is a prefect synonym to functionality of this framework. It replies to you with your request results.
But I thought it'll sound more epic with "r" at the end. So here is why ^^
-
To Vsevolod Ambros, the man the idea of
Iahr
was stolen from. -
To my university/employer, that will drop me out for doing this and not studying/working