Command handler Telethon - python

I started building a bot with the Telethon library but couldn't find a clean way to separate the command from the text.
For example, with the python-telegram-bot library, I can retrieve the first argument after the command like this:
def test(update, context):
arg_1 = context.args[0]
print(arg_1) # Prints the first word after the command
So... There's a way to make something like this using Telethon? (I'm using this piece of code to split text from command but I think this method is not really good):
txt = event.message.message.lower()
try:
txt.split("!raw ")[1]
text = event.message.message[4:]
except Exception as e:
text = ""
if text:
do_something()

If you have a handler defined in the following way:
#client.on(events.NewMessage(pattern=r'!raw (\w+)'))
async def handler(event):
...
You can then access the event.pattern_match:
arg = event.pattern_match.group(1)
print(arg) # first word
However, using .split() is also okay:
parts = event.raw_text.split()
if len(parts) > 1:
arg = parts[1]
(And you can also build a better UX, by telling the user when they used the command wrong.)

Related

Load item description from json file

Recently i saw a post about someone making a program that could control a computer it was launched on. (it was this one) Add commands to user input
I was really interested in it and I wanted to replicate it and improve my python skills on the way.
After watching some tutorials I had the ability to send and recieve emails and started working on some commands. First I added the ability to take screenshots as its the most important one. I then added functions and commands to do other stuff. Then I wanted to add a help command to display all commands if there is no args and the description of a specific command if there is an args. I first added the one without args and this is the code for it:
import json
user_input = "$say hello\n$help"
def help(*args):
if args == ():
for func_name, aliases in info_json.items():
print(func_name)
else:
pass
#print the description for the command
def command1():
print("I am command 1.")
def command2():
print("I am command 2.")
def command3():
print("I am command 3.")
def say(*args):
print(f"You said i should say \"{' '.join(args)}\"! Very cool :D")
def pause(sec):
print(f"I waited for {sec} seconds!")
commands = {
"$help":help,
"$pause":pause,
"$say":say,
"$command1":command1,
"$command2":command2,
"$command3":command3,
}
with open("commands.json") as json_file:
help_json = json.load(json_file)
def call_command(BEFEHL):
function, *args = BEFEHL.split(' ')
commands[function](*args)
for line in user_input.split("\n"):
try:
call_command(line)
except KeyError:
print("This command does not exist.")
I replaced the actual functions with print statements like the original author did :D
This code worked very well and I started to work on the description on specific functions. I created the commands.json example:
{
"command1": ["This command is command 1. It prints out 'I am command 1' "],
"command2": ["This command is command 2. It prints out 'I am command 2' "],
"command3": ["This command is command 3. It prints out 'I am command 3' "]
}
Is there any way you can print out the stuff in the json which stands behind the command? An example use would be:
>>> $help command1
print("This is command 1. It prints out 'I am command 1' ")
I would really appreciate to know if this is possible! :D
When you load a json, it basically acts like a Python dictionary, so you can retrieve the description of the command from its key, which you are passing as parameter.
Your help() function should look something like this:
def help(*args):
if args == ():
for func_name, aliases in help_json.items():
print(func_name)
else:
print(help_json.get(args[0], "Command does not exist"))
The second argument "Command does not exist" is the default value to print when the get() cannot find the key in the dictionary.

How would I convert user input into function arguments?

I'm trying to make a custom command line to control a robotic arm.
So I want to be able to run the program and type in servoMove(arg1,arg2) and have arg1 and arg2 get transferred into the function servoMove.
servoPos = [0,1,2,3,4]
def servoMove(servo,angle):
servoPos[servo] = angle
print(servoPos[servo])
def commands(cmd):
if cmd == 'servoMove('+arg1+','+arg2+')':
servoMove(arg1,arg2)
else:
print("[Error] - Unknown Command")
commands(input(""))
Clearly, the code below doesn't work for this.
if cmd == 'servoMove('+arg1+','+arg2+')':
servoMove(arg1,arg2)
Does anybody know how I can do this?
You can use a regular expression to parse the command.
import re
def commands(cmd):
m = re.match(r'servoMove\((\d+),(\d+)\)', cmd)
if m:
servoMove(int(m.group(1)), int(m.group(2)))
return
# Put similar tests for other commands here
# ...
print("[Error] - Unknown Command")
This is a really crude way to do it -- if the user doesn't enter the command exactly right it will complain that the command is unknown. If you want something more robust, you need to learn how to write a real parser. Or use a better user interface, such as Tkinter to implement a form that the user can fill out.
You can use the cmd module to build a command line interface.
Here's an example:
import cmd
servoPos = [0,1,2,3,4]
def servoMove(servo,angle):
servoPos[servo] = angle
print(servoPos[servo])
class ServoShell(cmd.Cmd):
prompt = '=> '
def do_servoMove(self, arg):
'Edit this to give a description to the function when typing ?'
servoMove(*parse(arg))
def parse(arg):
'Convert a comma separated string into a tuple'
return tuple(map(int, arg.strip('()').split(',')))
if __name__ == '__main__':
ServoShell().cmdloop()
Just looking at the structure the problem is in the if statement: arg1 and arg2 are undefined at that stage, so you'll get a False. For starters you'd want to replace that with something like:
#Look at the nine first characters to see if they match your function
if cmd[:9] == 'servoMove':
To extract your arguments, I'd use some string manipulation as in here. I've sliced the input to take the text between "(" and "," as arg1, and "," and ")" as arg2.
arg1 = cmd[cmd.find("(")+1:cmd.find(",")]
arg2 = cmd[cmd.find(",")+1:cmd.find(")")]
Putting it together:
def commands(cmd):
if cmd[:9] == 'servoMove':
arg1 = cmd[cmd.find("(")+1:cmd.find(",")]
arg2 = cmd[cmd.find(",")+1:cmd.find(")")]
servoMove(arg1, arg2)
else:
print("[Error] - Unknown Command")

cli: how-to initialise a dict and group click functions

I would like to initialise a global variable, in this case a dict called DOC, after passing a number of command line arguments and using the click library.
I have tried the following:
#!/usr/bin/python3
import os
import sys
import yaml
import logging
import click
DOC = {}
#click.group()
def cli():
pass
#click.command()
#click.option("--logger-file", required=True, default='{}/blabla/cfg/logger.{}.yml'.format(os.environ['HOME'],os.path.basename(__file__)), show_default=True, help="YAML logging configuration file")
def cli_logger_file(logger_file):
if os.path.exists(logger_file):
try:
with open(logger_file, "rt") as f:
DOC = yaml.safe_load(f.read())
print( "logger" )
except Exception as e:
print( str(e) )
sys.exit()
else:
sys.exit()
if __name__ == '__main__':
cli_logger_file()
print( "hi!" )
print( DOC )
But when I run it, the output is:
$ python3 etc.py --logger-file=/home/blabla/cfg/logger.src.app.component.yml
logger
{}
Could you please help me understand:
Why I do not see hi! being printed?
Why if I replace #click.command() with #cli.command() it does not recognise the command-line option --logger-file?
A couple of misunderstandings about how click works.
Why I do not see hi! being printed?
Click is a framework for writing cli programs. After the framework calls your handlers, it does not return...
What is #click.group()?
This question:
Why if I replace #click.command() with #cli.command() it does not recognize the command-line option --logger-file ?
is related to what #click.group() does. A group is a special processor intended to implement sub commands. So in your case, using a group click will parse any --flags before the subcommand. But you don't have any subcommands so the --flags will be consumed by the group. Just remove the group as you don't need it.
Code:
#click.command()
#click.option("--logger-file",
default=os.path.join(os.path.expanduser("~"),
'blabla/cfg/logger.{}.yml'.format(
os.path.basename(__file__))),
show_default=True,
help="YAML logging configuration file")
def cli(logger_file):
if os.path.exists(logger_file):
try:
with open(logger_file, "rt") as f:
global DOC
DOC = yaml.safe_load(f.read())
except Exception as e:
click.echo(str(e))
sys.exit()
click.echo('DOC: %s' % DOC)
if __name__ == '__main__':
cli()
Notes:
You had set the --loggerfile to required but also specifying a default.
I used os.path.expanduser() instead of directly using an environment variable.
In setting the variable DOC, you need to tell python it is a global.
But, why a global? After you understand the answer to the first question at the top of this post, you will realize that any functionality that this program implements will need to be called from the the same function that you are parsing the yaml in. So, you likely should just pass it as a variable....
Assigning to a global variable from a function requires a global declaration.
Group commands are invoked by name, so when you use #cli.command you need to write:
$ python3 etc.py cli_logger_file --logger-file=foo.yml

Python Command Line Arguments Try/Except

I want to create a program that will take two command line arguments. The first being the name of a file to open for parsing and the second the flag -s. If the user provides the wrong number of arguments or the other argument is not -s then it will print the message "Usage: [-s] file_name" and terminate the program using exit.
Next, I want my program to attempt to open the file for reading. The program should open the file read each line and return a count of every float, integer, and other kinds of strings that are not ints or floats. However, if opening the file fails it should raise an exception and print "Unable to open [filename]" and quit using exit.
I've been looking up lots of stuff on the internet about command lines in Python but I've ended up more confused. So here's my attempt at it so far from what I've researched.
from optparse import OptionParser
def command_line():
parser = OptionParser()
parser.add_option("--file", "-s")
options, args = parser.parse_args()
if options.a and obtions.b:
parser.error("Usage: [-s] file_name")
exit
def read_file():
#Try:
#Open input file
#Except:
#print "Unable to open [filename]"
#Exit
from optparse import OptionParser
import sys,os
def command_line():
parser = OptionParser("%prog [-s] file_name")
parser.add_option("-s",dest="filename",
metavar="file_name",help="my help message")
options, args = parser.parse_args()
if not options.filename:
parser.print_help()
sys.exit()
return options.filename
def read_file(fn):
if os.path.isfile(fn):
typecount = {}
with open(fn) as f:
for line in f:
for i in line.split()
try:
t = type(eval(i))
except NameError:
t = type(i)
if t in typecount:
typecount[t] += 1
else:
typecount[t] = 1
else:
print( "Unable to open {}".format(fn))
sys.exit()
print(typecount)
read_file(command_line())
So step by step:
options.a is not defined unless you define an option --a or (preferably) set dest="a".
using the built-in parser.print_help() is better than making your own, you get -h/--help for free then.
you never called your function command_line, therefore never getting any errors, as the syntax was correct. I set the commandline to pass only the filename as a return value, but there are better ways of doing this for when you have more options/arguments.
When it comes to read_file, instead of using try-except for the file I recommend using os.path.isfile which will check whether the file exists. This does not check that the file has the right format though.
We then create a dictionary of types, then loop over all lines and evaluate objects which are separated by whitespace(space,newline,tab). If your values are separated by eg. a comma, you need to use line.split(',').
If you want to use the counts later in your script, you might want to return typecount instead of printing it.

How to use subprocess to execute programs with Python

Hello i am using the subprocess.Popen() class and i succesful execute commands on the terminal, but when i try to execute programs for example an script written on Python and i try to pass arguments the system fails.
This is the code:
argPath = "test1"
args = open(argPath, 'w')
if self.extract.getByAttr(self.block, 'name', 'args') != None:
args.write("<request>"+self.extract.getByAttr(self.block, 'name', 'args')[0].toxml()+"</request>")
else:
args.write('')
car = Popen(shlex.split('python3.1 /home/hidura/webapps/karinapp/Suite/ForeingCode/saveCSS.py', stdin=args, stdout=subprocess.PIPE, stderr=subprocess.PIPE))
args.close()
dataOut = car.stdout.read().decode()
log = car.stderr.read().decode()
if dataOut!='':
return dataOut.split('\n')
elif log != '':
return log.split('\n')[0]
else:
return None
And the code from the saveCSS.py
from xml.dom.minidom import parseString
import os
import sys
class savCSS:
"""This class has to save
the changes on the css file.
"""
def __init__(self, args):
document = parseString(args)
request = document.firstChild
address = request.getElementsByTagName('element')[0]
newdata = request.getElementsByTagName('element')[1]
cssfl = open("/webapps/karinapp/Suite/"+address.getAttribute('value'), 'r')
cssData = cssfl.read()
cssfl.close()
dataCSS = ''
for child in newdata.childNodes:
if child.nodeType == 3:
dataCSS += child.nodeValue
nwcssDict = {}
for piece in dataCSS.split('}'):
nwcssDict[piece.split('{')[0]] = piece.split('{')[1]
cssDict = {}
for piece in cssData.split('}'):
cssDict[piece.split('{')[0]] = piece.split('{')[1]
for key in nwcssDict:
if key in cssDict == True:
del cssDict[key]
cssDict[key] = nwcssDict[key]
result = ''
for key in cssDict:
result += key+"{"+cssDict[key]+"}"
cssfl = open(cssfl.name, 'a')
cssfl.write(result)
cssfl.close()
if __name__ == "__main__":
savCSS(sys.stdin)
BTW: There's no output...
Thanks in advance.
OK, I'm ignoring that your code doesn't run (neither the script you try to execute, not the main script actually works), and looking at what you are doing:
It does execute the script, or you would get an error, like "bin/sh: foo: not found".
Also you seem to be using an open file as stdin after you have written to it. That doesn't work.
>>> thefile = open('/tmp/foo.txt', 'w')
>>> thefile.write("Hej!")
4
>>> thefile.read()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IOError: not readable
You need to close the file, and reopen it as a read file. Although better in this case would be to use StringIO, I think.
To talk to the subprocess, you use communicate(), not read() on the pipes.
I'm not sure why you are using shell=True here, it doesn't seem necessary, I would remove it if I was you, it only complicates stuff unless you actually need the shell to do things.
Specifically you should not split the command into a list when using shell=True. What your code is actually doing, is starting a Python prompt.
You should rather use communicate() instead of .stdout.read().
And the code you posted isn't even correct:
Popen(shlex.split('python3.1 /home/hidura/webapps/karinapp/Suite/ForeingCode/saveCSS.py', stdin=args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
There's a missing parenthesis, and from the stdout/stderr parameters, it's clear that you get no output to the console, but rather into pipes (if that's what you meant by "There's no output...").
Your code will actually work on Windows, but on Linux you must remove the shell=True parameter. You should always omit that parameter if you provide the full command line yourself (as a sequence).

Categories

Resources