move arg stuff to its own file
This commit is contained in:
parent
ec00796cc0
commit
b989aaeea2
|
@ -1,12 +1,10 @@
|
||||||
from typing import Dict, List, Union
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from telethon import TelegramClient
|
from telethon import TelegramClient
|
||||||
from telethon.tl.custom.message import Message
|
from telethon.tl.custom.message import Message
|
||||||
from cyber_fenneko.internal.constants import GLOBAL_ARGS
|
from cyber_fenneko.internal.constants import GLOBAL_ARGS
|
||||||
from cyber_fenneko.internal.command import Command
|
from cyber_fenneko.internal.command import Command
|
||||||
from cyber_fenneko.internal.command_context import CommandContext
|
from cyber_fenneko.internal.command_context import CommandContext
|
||||||
from cyber_fenneko.internal.arg import Arg, ArgType
|
from cyber_fenneko.internal.arg_parser import parse_args
|
||||||
|
|
||||||
class Bot:
|
class Bot:
|
||||||
def __init__(self, client: TelegramClient):
|
def __init__(self, client: TelegramClient):
|
||||||
|
@ -90,100 +88,3 @@ class Bot:
|
||||||
# if the command is marked as 'delete', delete the message that triggered it
|
# if the command is marked as 'delete', delete the message that triggered it
|
||||||
if ctx.args['delete']:
|
if ctx.args['delete']:
|
||||||
await event.message.delete()
|
await event.message.delete()
|
||||||
|
|
||||||
def parse_args(self, text: str, cmd_args: List[Arg]) -> (Dict[str, Arg], str):
|
|
||||||
"""
|
|
||||||
Take an incoming string and parse args from it.
|
|
||||||
Args are defined one of three ways:
|
|
||||||
- name:value
|
|
||||||
- .name - shorthand for name:true
|
|
||||||
- !name - shorthand for name:false
|
|
||||||
Args are also only valid at the beginning of a message,
|
|
||||||
so as soon as we encounter a non-arg we stop parsing.
|
|
||||||
"""
|
|
||||||
args: Dict[str, Arg] = { arg.name: arg for arg in cmd_args }
|
|
||||||
aliases: Dict[str, str] = { alias: arg.name for arg in cmd_args for alias in arg.aliases }
|
|
||||||
|
|
||||||
get_arg = lambda name: args.get(name) or args.get(aliases.get(name))
|
|
||||||
|
|
||||||
parsed_args: Dict[str, Union[str, int, bool]] = {}
|
|
||||||
tokens = text.split()
|
|
||||||
while tokens:
|
|
||||||
token = tokens.pop(0)
|
|
||||||
|
|
||||||
if token.startswith('.'):
|
|
||||||
# shorthand for name:true
|
|
||||||
arg = get_arg(token[1:])
|
|
||||||
if arg:
|
|
||||||
if args[arg.name].type == ArgType.bool:
|
|
||||||
parsed_args[arg.name] = True
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Arg "{arg.name}" is not a boolean, but a boolean was provided')
|
|
||||||
else:
|
|
||||||
self.logger.warning(f'Unknown arg "{arg.name}"')
|
|
||||||
continue
|
|
||||||
elif token.startswith('!'):
|
|
||||||
# shorthand for name:false
|
|
||||||
arg = get_arg(token[1:])
|
|
||||||
if arg:
|
|
||||||
if arg.type == ArgType.bool:
|
|
||||||
parsed_args[arg.name] = False
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Arg "{arg.name}" is not a boolean, but a boolean was provided')
|
|
||||||
else:
|
|
||||||
self.logger.warning(f'Unknown arg "{arg.name}"')
|
|
||||||
continue
|
|
||||||
elif ':' in token:
|
|
||||||
# name:value
|
|
||||||
# value might be a quoted string, so we need to handle that
|
|
||||||
arg_name, arg_value = token.split(':', 1)
|
|
||||||
arg = get_arg(arg_name)
|
|
||||||
if arg:
|
|
||||||
if arg.type == ArgType.str:
|
|
||||||
# we need to check if the value starts with a quote,
|
|
||||||
# if so we need to loop through tokens until we find the end quote
|
|
||||||
if arg_value.startswith('"'):
|
|
||||||
while not arg_value.endswith('"') and tokens:
|
|
||||||
arg_value += ' ' + tokens.pop(0)
|
|
||||||
if not arg_value.endswith('"'):
|
|
||||||
raise ValueError(f'Unterminated string for arg "{arg.name}"')
|
|
||||||
arg_value = arg_value[1:-1]
|
|
||||||
parsed_args[arg.name] = arg_value
|
|
||||||
elif arg.type == ArgType.bool:
|
|
||||||
# if the arg is a boolean, we need to check if the value is true or false
|
|
||||||
if arg_value.lower() == 'true':
|
|
||||||
parsed_args[arg.name] = True
|
|
||||||
elif arg_value.lower() == 'false':
|
|
||||||
parsed_args[arg.name] = False
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Invalid boolean value {arg_value} for arg "{arg.name}"')
|
|
||||||
elif arg.type == ArgType.int:
|
|
||||||
# if the arg is an int, we need to check if the value is an int
|
|
||||||
try:
|
|
||||||
parsed_args[arg.name] = int(arg_value)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(f'Invalid int value {arg_value} for arg "{arg.name}"')
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Invalid arg type {arg.type} for arg "{arg.name}"')
|
|
||||||
else:
|
|
||||||
self.logger.warning(f'Unknown arg "{arg.name}"')
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If we get here, we've encountered a non-arg token
|
|
||||||
# so we stop parsing
|
|
||||||
break
|
|
||||||
|
|
||||||
# Check if any required args are missing
|
|
||||||
missing_args = [arg.name for arg in cmd_args if arg.required and arg.name not in parsed_args]
|
|
||||||
if missing_args:
|
|
||||||
raise ValueError(f'Missing required args: {", ".join(missing_args)}')
|
|
||||||
|
|
||||||
# remove the parsed args from the text
|
|
||||||
text = ' '.join(tokens[len(parsed_args):])
|
|
||||||
|
|
||||||
# add the default values for any args that weren't specified
|
|
||||||
for arg in args.values():
|
|
||||||
if arg.name not in parsed_args:
|
|
||||||
parsed_args[arg.name] = arg.default
|
|
||||||
|
|
||||||
return parsed_args, text
|
|
|
@ -6,7 +6,7 @@ from collections import OrderedDict
|
||||||
from telethon.tl.types import TypeMessageEntity, MessageEntityPre
|
from telethon.tl.types import TypeMessageEntity, MessageEntityPre
|
||||||
from .. import bot
|
from .. import bot
|
||||||
from ..internal.command_context import CommandContext
|
from ..internal.command_context import CommandContext
|
||||||
from ..internal.arg import Arg, ArgType
|
from ..internal.arg_parser import Arg, ArgType
|
||||||
|
|
||||||
ENDPOINT_URL = 'https://inkify.0x45.st'
|
ENDPOINT_URL = 'https://inkify.0x45.st'
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from telethon.tl.types import MessageEntityPre
|
||||||
from telethon.tl.custom.message import Message
|
from telethon.tl.custom.message import Message
|
||||||
from .. import bot
|
from .. import bot
|
||||||
from ..internal.command_context import CommandContext
|
from ..internal.command_context import CommandContext
|
||||||
from ..internal.arg import Arg, ArgType
|
from ..internal.arg_parser import Arg, ArgType
|
||||||
|
|
||||||
ENDPOINT = 'https://0x45.st/api/pastes'
|
ENDPOINT = 'https://0x45.st/api/pastes'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import time
|
import time
|
||||||
from .. import bot
|
from .. import bot
|
||||||
from ..internal.command_context import CommandContext
|
from ..internal.command_context import CommandContext
|
||||||
from ..internal.arg import Arg, ArgType
|
from ..internal.arg_parser import Arg, ArgType
|
||||||
|
|
||||||
@bot.command(
|
@bot.command(
|
||||||
'ping',
|
'ping',
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
from enum import Enum
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
ArgType = Enum('ArgType', ['str', 'int', 'bool', 'peer_id'])
|
|
||||||
|
|
||||||
class Arg:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
*,
|
|
||||||
# The type of this argument
|
|
||||||
type: ArgType = ArgType.str,
|
|
||||||
|
|
||||||
# Aliases for this argument
|
|
||||||
aliases: List[str] = [],
|
|
||||||
|
|
||||||
# Is this argument required?
|
|
||||||
required: bool = False,
|
|
||||||
|
|
||||||
# The default value for this argument
|
|
||||||
default: any = None,
|
|
||||||
|
|
||||||
# The description for this argument, displayed in the help command
|
|
||||||
description: str = None,
|
|
||||||
|
|
||||||
# Special designation for global args
|
|
||||||
global_arg: bool = False,
|
|
||||||
):
|
|
||||||
self.name = name
|
|
||||||
self.type = type
|
|
||||||
self.aliases = aliases
|
|
||||||
self.required = required
|
|
||||||
self.default = default
|
|
||||||
self.description = description
|
|
||||||
self.global_arg = global_arg
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Dict, List, Union
|
||||||
|
|
||||||
|
|
||||||
|
ArgType = Enum('ArgType', ['str', 'int', 'bool', 'peer_id'])
|
||||||
|
|
||||||
|
class Arg:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
*,
|
||||||
|
# The type of this argument
|
||||||
|
type: ArgType = ArgType.str,
|
||||||
|
|
||||||
|
# Aliases for this argument
|
||||||
|
aliases: List[str] = [],
|
||||||
|
|
||||||
|
# Is this argument required?
|
||||||
|
required: bool = False,
|
||||||
|
|
||||||
|
# The default value for this argument
|
||||||
|
default: any = None,
|
||||||
|
|
||||||
|
# The description for this argument, displayed in the help command
|
||||||
|
description: str = None,
|
||||||
|
|
||||||
|
# Special designation for global args
|
||||||
|
global_arg: bool = False,
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.type = type
|
||||||
|
self.aliases = aliases
|
||||||
|
self.required = required
|
||||||
|
self.default = default
|
||||||
|
self.description = description
|
||||||
|
self.global_arg = global_arg
|
||||||
|
|
||||||
|
def parse_args(self, text: str, cmd_args: List[Arg]) -> (Dict[str, Arg], str):
|
||||||
|
"""
|
||||||
|
Take an incoming string and parse args from it.
|
||||||
|
Args are defined one of three ways:
|
||||||
|
- name:value
|
||||||
|
- .name - shorthand for name:true
|
||||||
|
- !name - shorthand for name:false
|
||||||
|
Args are also only valid at the beginning of a message,
|
||||||
|
so as soon as we encounter a non-arg we stop parsing.
|
||||||
|
"""
|
||||||
|
args: Dict[str, Arg] = { arg.name: arg for arg in cmd_args }
|
||||||
|
aliases: Dict[str, str] = { alias: arg.name for arg in cmd_args for alias in arg.aliases }
|
||||||
|
|
||||||
|
get_arg = lambda name: args.get(name) or args.get(aliases.get(name))
|
||||||
|
|
||||||
|
parsed_args: Dict[str, Union[str, int, bool]] = {}
|
||||||
|
tokens = text.split()
|
||||||
|
while tokens:
|
||||||
|
token = tokens.pop(0)
|
||||||
|
|
||||||
|
if token.startswith('.'):
|
||||||
|
# shorthand for name:true
|
||||||
|
arg = get_arg(token[1:])
|
||||||
|
if arg:
|
||||||
|
if args[arg.name].type == ArgType.bool:
|
||||||
|
parsed_args[arg.name] = True
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Arg "{arg.name}" is not a boolean, but a boolean was provided')
|
||||||
|
else:
|
||||||
|
self.logger.warning(f'Unknown arg "{arg.name}"')
|
||||||
|
continue
|
||||||
|
elif token.startswith('!'):
|
||||||
|
# shorthand for name:false
|
||||||
|
arg = get_arg(token[1:])
|
||||||
|
if arg:
|
||||||
|
if arg.type == ArgType.bool:
|
||||||
|
parsed_args[arg.name] = False
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Arg "{arg.name}" is not a boolean, but a boolean was provided')
|
||||||
|
else:
|
||||||
|
self.logger.warning(f'Unknown arg "{arg.name}"')
|
||||||
|
continue
|
||||||
|
elif ':' in token:
|
||||||
|
# name:value
|
||||||
|
# value might be a quoted string, so we need to handle that
|
||||||
|
arg_name, arg_value = token.split(':', 1)
|
||||||
|
arg = get_arg(arg_name)
|
||||||
|
if arg:
|
||||||
|
if arg.type == ArgType.str:
|
||||||
|
# we need to check if the value starts with a quote,
|
||||||
|
# if so we need to loop through tokens until we find the end quote
|
||||||
|
if arg_value.startswith('"'):
|
||||||
|
while not arg_value.endswith('"') and tokens:
|
||||||
|
arg_value += ' ' + tokens.pop(0)
|
||||||
|
if not arg_value.endswith('"'):
|
||||||
|
raise ValueError(f'Unterminated string for arg "{arg.name}"')
|
||||||
|
arg_value = arg_value[1:-1]
|
||||||
|
parsed_args[arg.name] = arg_value
|
||||||
|
elif arg.type == ArgType.bool:
|
||||||
|
# if the arg is a boolean, we need to check if the value is true or false
|
||||||
|
if arg_value.lower() == 'true':
|
||||||
|
parsed_args[arg.name] = True
|
||||||
|
elif arg_value.lower() == 'false':
|
||||||
|
parsed_args[arg.name] = False
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Invalid boolean value {arg_value} for arg "{arg.name}"')
|
||||||
|
elif arg.type == ArgType.int:
|
||||||
|
# if the arg is an int, we need to check if the value is an int
|
||||||
|
try:
|
||||||
|
parsed_args[arg.name] = int(arg_value)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(f'Invalid int value {arg_value} for arg "{arg.name}"')
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Invalid arg type {arg.type} for arg "{arg.name}"')
|
||||||
|
else:
|
||||||
|
self.logger.warning(f'Unknown arg "{arg.name}"')
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we get here, we've encountered a non-arg token
|
||||||
|
# so we stop parsing
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check if any required args are missing
|
||||||
|
missing_args = [arg.name for arg in cmd_args if arg.required and arg.name not in parsed_args]
|
||||||
|
if missing_args:
|
||||||
|
raise ValueError(f'Missing required args: {", ".join(missing_args)}')
|
||||||
|
|
||||||
|
# remove the parsed args from the text
|
||||||
|
text = ' '.join(tokens[len(parsed_args):])
|
||||||
|
|
||||||
|
# add the default values for any args that weren't specified
|
||||||
|
for arg in args.values():
|
||||||
|
if arg.name not in parsed_args:
|
||||||
|
parsed_args[arg.name] = arg.default
|
||||||
|
|
||||||
|
return parsed_args, text
|
|
@ -1,7 +1,7 @@
|
||||||
import itertools
|
import itertools
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from .arg import Arg
|
from .arg_parser import Arg
|
||||||
from ..bot import GLOBAL_ARGS
|
from ..bot import GLOBAL_ARGS
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
|
|
|
@ -4,7 +4,7 @@ from telethon.events import NewMessage
|
||||||
from telethon.tl.custom.message import Message
|
from telethon.tl.custom.message import Message
|
||||||
|
|
||||||
from .. import bot
|
from .. import bot
|
||||||
from .arg import Arg
|
from .arg_parser import Arg
|
||||||
|
|
||||||
|
|
||||||
class CommandContext:
|
class CommandContext:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from .arg import Arg, ArgType
|
from .arg_parser import Arg, ArgType
|
||||||
|
|
||||||
# Args that exist on every command
|
# Args that exist on every command
|
||||||
GLOBAL_ARGS = [
|
GLOBAL_ARGS = [
|
||||||
|
|
Loading…
Reference in New Issue