witchie/toot/console.py

373 lines
9.9 KiB
Python
Raw Normal View History

2017-04-15 12:53:08 +00:00
# -*- coding: utf-8 -*-
2018-01-14 14:34:41 +00:00
import os
2017-04-12 14:42:04 +00:00
import sys
2017-04-19 12:47:30 +00:00
import logging
2017-04-12 14:42:04 +00:00
from argparse import ArgumentParser, FileType
2017-04-19 12:47:30 +00:00
from collections import namedtuple
2017-12-30 12:32:52 +00:00
from toot import config, commands, CLIENT_NAME, CLIENT_WEBSITE
from toot.exceptions import ApiError, ConsoleError
from toot.output import print_out, print_err
2017-04-19 12:47:30 +00:00
VISIBILITY_CHOICES = ['public', 'unlisted', 'private', 'direct']
2017-04-19 12:47:30 +00:00
def visibility(value):
"""Validates the visibilty parameter"""
if value not in VISIBILITY_CHOICES:
raise ValueError("Invalid visibility value")
2017-04-19 12:47:30 +00:00
return value
2017-04-19 12:47:30 +00:00
Command = namedtuple("Command", ["name", "description", "require_auth", "arguments"])
common_args = [
(["--no-color"], {
"help": "don't use ANSI colors in output",
"action": 'store_true',
"default": False,
}),
(["--debug"], {
"help": "show debug log in console",
"action": 'store_true',
"default": False,
})
]
2017-04-26 09:49:21 +00:00
account_arg = (["account"], {
"help": "account name, e.g. 'Gargron@mastodon.social'",
2017-04-26 09:49:21 +00:00
})
instance_arg = (["-i", "--instance"], {
"type": str,
"help": 'mastodon instance to log into e.g. "mastodon.social"',
})
email_arg = (["-e", "--email"], {
"type": str,
"help": 'email address to log in with',
})
2017-04-26 09:49:21 +00:00
AUTH_COMMANDS = [
2017-04-19 12:47:30 +00:00
Command(
name="login",
description="Log in from the console, does NOT support two factor authentication",
arguments=[instance_arg, email_arg],
require_auth=False,
),
Command(
name="login_browser",
description="Log in using your browser, supports regular and two factor authentication",
arguments=[instance_arg],
require_auth=False,
),
Command(
name="activate",
description="Switch between logged in accounts.",
arguments=[account_arg],
2017-04-19 12:47:30 +00:00
require_auth=False,
),
Command(
name="logout",
description="Log out, delete stored access keys",
arguments=[account_arg],
2017-04-19 12:47:30 +00:00
require_auth=False,
),
Command(
name="auth",
description="Show logged in accounts and instances",
2017-04-19 12:47:30 +00:00
arguments=[],
require_auth=False,
),
]
READ_COMMANDS = [
2017-04-19 12:47:30 +00:00
Command(
name="whoami",
description="Display logged in user details",
arguments=[],
require_auth=True,
),
2017-04-19 13:29:40 +00:00
Command(
name="whois",
description="Display account details",
2017-04-19 13:29:40 +00:00
arguments=[
(["account"], {
"help": "account name or numeric ID"
}),
],
require_auth=True,
),
2017-12-29 13:26:40 +00:00
Command(
name="instance",
description="Display instance details",
arguments=[
(["instance"], {
"help": "instance domain (e.g. 'mastodon.social') or blank to use current",
"nargs": "?",
}),
],
require_auth=False,
),
Command(
name="search",
description="Search for users or hashtags",
arguments=[
(["query"], {
"help": "the search query",
}),
(["-r", "--resolve"], {
"action": 'store_true',
"default": False,
"help": "Resolve non-local accounts",
}),
],
require_auth=True,
),
Command(
name="timeline",
2018-06-12 08:40:36 +00:00
description="Show recent items in a timeline (home by default)",
arguments=[
2018-06-12 08:40:36 +00:00
(["-p", "--public"], {
"action": "store_true",
"default": False,
"help": "Show public timeline.",
}),
(["-t", "--tag"], {
"type": str,
"help": "Show timeline for given hashtag.",
}),
(["-i", "--list"], {
2018-06-12 08:40:36 +00:00
"type": int,
"help": "Show timeline for given list ID.",
}),
(["-l", "--local"], {
2018-06-12 08:40:36 +00:00
"action": "store_true",
"default": False,
2018-06-12 08:40:36 +00:00
"help": "Show only statuses from local instance (public and tag timelines only).",
}),
],
require_auth=True,
),
Command(
name="curses",
description="An experimental timeline app (doesn't work on Windows)",
2018-01-06 10:25:05 +00:00
arguments=[
(["-p", "--public"], {
"action": 'store_true',
"default": False,
"help": "Resolve non-local accounts",
}),
(["-i", "--instance"], {
"type": str,
"help": 'instance from which to read (for public timeline only)',
})
],
require_auth=False,
),
]
POST_COMMANDS = [
2017-04-19 12:47:30 +00:00
Command(
name="post",
description="Post a status text to your timeline",
arguments=[
(["text"], {
"help": "The status text to post.",
"nargs": "?",
2017-04-19 12:47:30 +00:00
}),
(["-m", "--media"], {
"type": FileType('rb'),
"help": "path to the media file to attach"
}),
(["-v", "--visibility"], {
"type": visibility,
"default": "public",
"help": 'post visibility, one of: %s' % ", ".join(VISIBILITY_CHOICES),
}),
(["-s", "--sensitive"], {
"action": 'store_true',
"default": False,
"help": "mark the media as NSFW",
}),
(["-p", "--spoiler-text"], {
"type": str,
"help": 'text to be shown as a warning before the actual content',
2017-04-19 12:47:30 +00:00
})
],
require_auth=True,
),
Command(
name="upload",
description="Upload an image or video file",
arguments=[
(["file"], {
"help": "Path to the file to upload",
"type": FileType('rb')
})
],
require_auth=True,
),
]
ACCOUNTS_COMMANDS = [
2017-04-19 12:47:30 +00:00
Command(
name="follow",
description="Follow an account",
arguments=[
2017-04-26 09:49:21 +00:00
account_arg,
2017-04-19 12:47:30 +00:00
],
require_auth=True,
),
Command(
name="unfollow",
description="Unfollow an account",
arguments=[
2017-04-26 09:49:21 +00:00
account_arg,
],
require_auth=True,
),
Command(
name="mute",
description="Mute an account",
arguments=[
account_arg,
],
require_auth=True,
),
Command(
name="unmute",
description="Unmute an account",
arguments=[
account_arg,
],
require_auth=True,
),
Command(
name="block",
description="Block an account",
arguments=[
account_arg,
],
require_auth=True,
),
Command(
name="unblock",
description="Unblock an account",
arguments=[
account_arg,
2017-04-19 12:47:30 +00:00
],
require_auth=True,
),
]
COMMANDS = AUTH_COMMANDS + READ_COMMANDS + POST_COMMANDS + ACCOUNTS_COMMANDS
2017-04-12 14:42:04 +00:00
def print_usage():
max_name_len = max(len(command.name) for command in COMMANDS)
groups = [
("Authentication", AUTH_COMMANDS),
("Read", READ_COMMANDS),
("Post", POST_COMMANDS),
("Accounts", ACCOUNTS_COMMANDS),
]
print_out("<green>{}</green>".format(CLIENT_NAME))
2017-04-19 12:47:30 +00:00
for name, cmds in groups:
print_out("")
print_out(name + ":")
2017-04-19 12:47:30 +00:00
for cmd in cmds:
cmd_name = cmd.name.ljust(max_name_len + 2)
print_out(" <yellow>toot {}</yellow> {}".format(cmd_name, cmd.description))
2017-04-19 12:47:30 +00:00
print_out("")
print_out("To get help for each command run:")
print_out(" <yellow>toot <command> --help</yellow>")
print_out("")
print_out("<green>{}</green>".format(CLIENT_WEBSITE))
2017-04-12 14:42:04 +00:00
2017-04-19 12:47:30 +00:00
def get_argument_parser(name, command):
parser = ArgumentParser(
prog='toot %s' % name,
description=command.description,
epilog=CLIENT_WEBSITE)
2017-04-12 14:42:04 +00:00
for args, kwargs in command.arguments + common_args:
2017-04-19 12:47:30 +00:00
parser.add_argument(*args, **kwargs)
2017-04-12 14:42:04 +00:00
# If the command requires auth, give an option to select account
if command.require_auth:
parser.add_argument("-u", "--using", help="the account to use, overrides active account")
2017-04-19 12:47:30 +00:00
return parser
2017-04-12 14:42:04 +00:00
2017-04-15 10:12:33 +00:00
2017-04-19 12:47:30 +00:00
def run_command(app, user, name, args):
command = next((c for c in COMMANDS if c.name == name), None)
2017-04-12 14:42:04 +00:00
2017-04-19 12:47:30 +00:00
if not command:
print_err("Unknown command '{}'\n".format(name))
2017-04-19 12:47:30 +00:00
print_usage()
2017-04-16 15:15:05 +00:00
return
2017-04-19 12:47:30 +00:00
parser = get_argument_parser(name, command)
parsed_args = parser.parse_args(args)
2017-04-13 11:52:28 +00:00
# Override the active account if 'using' option is given
if command.require_auth and parsed_args.using:
user, app = config.get_user_app(parsed_args.using)
if not user or not app:
raise ConsoleError("User '{}' not found".format(parsed_args.using))
2017-04-19 12:47:30 +00:00
if command.require_auth and (not user or not app):
print_err("This command requires that you are logged in.")
print_err("Please run `toot login` first.")
2017-04-13 11:52:28 +00:00
return
2017-04-19 12:47:30 +00:00
fn = commands.__dict__.get(name)
2017-04-13 11:52:28 +00:00
2017-04-19 13:29:40 +00:00
if not fn:
raise NotImplementedError("Command '{}' does not have an implementation.".format(name))
2017-04-19 12:47:30 +00:00
return fn(app, user, parsed_args)
2017-04-13 11:52:28 +00:00
def main():
2018-01-14 14:34:41 +00:00
# Enable debug logging if --debug is in args
if "--debug" in sys.argv:
2018-01-14 14:34:41 +00:00
filename = os.getenv("TOOT_LOG_FILE")
logging.basicConfig(level=logging.DEBUG, filename=filename)
2017-04-12 14:42:04 +00:00
2017-04-19 09:03:44 +00:00
# If something is piped in, append it to commandline arguments
if not sys.stdin.isatty():
stdin = sys.stdin.read()
if stdin:
sys.argv.append(stdin)
2017-04-19 09:03:44 +00:00
2017-04-19 12:47:30 +00:00
command_name = sys.argv[1] if len(sys.argv) > 1 else None
args = sys.argv[2:]
2017-04-12 14:42:04 +00:00
2017-04-19 12:47:30 +00:00
if not command_name:
2017-04-13 11:52:28 +00:00
return print_usage()
user, app = config.get_active_user_app()
2017-04-19 12:47:30 +00:00
2017-04-13 11:52:28 +00:00
try:
2017-04-19 12:47:30 +00:00
run_command(app, user, command_name, args)
2017-04-13 11:52:28 +00:00
except ConsoleError as e:
print_err(str(e))
sys.exit(1)
2017-12-30 12:32:52 +00:00
except ApiError as e:
print_err(str(e))
sys.exit(1)