2023-11-30 20:12:04 +01:00
|
|
|
import click
|
|
|
|
import platform
|
|
|
|
import sys
|
|
|
|
import webbrowser
|
2023-12-07 19:41:30 +01:00
|
|
|
from click.shell_completion import CompletionItem
|
|
|
|
|
|
|
|
from click.types import StringParamType
|
2023-11-30 20:12:04 +01:00
|
|
|
|
|
|
|
from toot import api, config, __version__
|
|
|
|
from toot.auth import get_or_create_app, login_auth_code, login_username_password
|
|
|
|
from toot.cli.base import cli
|
|
|
|
from toot.cli.validators import validate_instance
|
|
|
|
|
|
|
|
|
|
|
|
instance_option = click.option(
|
|
|
|
"--instance", "-i", "base_url",
|
|
|
|
prompt="Enter instance URL",
|
|
|
|
default="https://mastodon.social",
|
|
|
|
callback=validate_instance,
|
|
|
|
help="""Domain or base URL of the instance to log into,
|
|
|
|
e.g. 'mastodon.social' or 'https://mastodon.social'""",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-12-07 19:41:30 +01:00
|
|
|
class AccountParamType(StringParamType):
|
|
|
|
"""Custom type to add shell completion for account names"""
|
|
|
|
|
|
|
|
def shell_complete(self, ctx, param, incomplete: str):
|
|
|
|
accounts = config.load_config()["users"].keys()
|
|
|
|
return [
|
|
|
|
CompletionItem(a)
|
|
|
|
for a in accounts
|
|
|
|
if a.lower().startswith(incomplete.lower())
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2023-11-30 20:12:04 +01:00
|
|
|
@cli.command()
|
|
|
|
def auth():
|
|
|
|
"""Show logged in accounts and instances"""
|
|
|
|
config_data = config.load_config()
|
|
|
|
|
|
|
|
if not config_data["users"]:
|
|
|
|
click.echo("You are not logged in to any accounts")
|
|
|
|
return
|
|
|
|
|
|
|
|
active_user = config_data["active_user"]
|
|
|
|
|
|
|
|
click.echo("Authenticated accounts:")
|
|
|
|
for uid, u in config_data["users"].items():
|
|
|
|
active_label = "ACTIVE" if active_user == uid else ""
|
|
|
|
uid = click.style(uid, fg="green")
|
|
|
|
active_label = click.style(active_label, fg="yellow")
|
|
|
|
click.echo(f"* {uid} {active_label}")
|
|
|
|
|
|
|
|
path = config.get_config_file_path()
|
|
|
|
path = click.style(path, "blue")
|
|
|
|
click.echo(f"\nAuth tokens are stored in: {path}")
|
|
|
|
|
|
|
|
|
|
|
|
@cli.command()
|
|
|
|
def env():
|
|
|
|
"""Print environment information for inclusion in bug reports."""
|
|
|
|
click.echo(f"toot {__version__}")
|
|
|
|
click.echo(f"Python {sys.version}")
|
|
|
|
click.echo(platform.platform())
|
|
|
|
|
|
|
|
|
|
|
|
@cli.command(name="login_cli")
|
|
|
|
@instance_option
|
|
|
|
@click.option("--email", "-e", help="Email address to log in with", prompt=True)
|
|
|
|
@click.option("--password", "-p", hidden=True, prompt=True, hide_input=True)
|
|
|
|
def login_cli(base_url: str, email: str, password: str):
|
|
|
|
"""
|
|
|
|
Log into an instance from the console (not recommended)
|
|
|
|
|
|
|
|
Does NOT support two factor authentication, may not work on instances
|
|
|
|
other than Mastodon, mostly useful for scripting.
|
|
|
|
"""
|
|
|
|
app = get_or_create_app(base_url)
|
|
|
|
login_username_password(app, email, password)
|
|
|
|
|
|
|
|
click.secho("✓ Successfully logged in.", fg="green")
|
|
|
|
click.echo("Access token saved to config at: ", nl=False)
|
|
|
|
click.secho(config.get_config_file_path(), fg="green")
|
|
|
|
|
|
|
|
|
|
|
|
LOGIN_EXPLANATION = """This authentication method requires you to log into your
|
|
|
|
Mastodon instance in your browser, where you will be asked to authorize toot to
|
|
|
|
access your account. When you do, you will be given an authorization code which
|
|
|
|
you need to paste here.""".replace("\n", " ")
|
|
|
|
|
|
|
|
|
|
|
|
@cli.command()
|
|
|
|
@instance_option
|
|
|
|
def login(base_url: str):
|
|
|
|
"""Log into an instance using your browser (recommended)"""
|
|
|
|
app = get_or_create_app(base_url)
|
|
|
|
url = api.get_browser_login_url(app)
|
|
|
|
|
|
|
|
click.echo(click.wrap_text(LOGIN_EXPLANATION))
|
|
|
|
click.echo("\nLogin URL:")
|
|
|
|
click.echo(url)
|
|
|
|
|
|
|
|
yesno = click.prompt("Open link in default browser? [Y/n]", default="Y", show_default=False)
|
|
|
|
if not yesno or yesno.lower() == 'y':
|
|
|
|
webbrowser.open(url)
|
|
|
|
|
|
|
|
authorization_code = ""
|
|
|
|
while not authorization_code:
|
|
|
|
authorization_code = click.prompt("Authorization code")
|
|
|
|
|
|
|
|
login_auth_code(app, authorization_code)
|
|
|
|
|
|
|
|
click.echo()
|
|
|
|
click.secho("✓ Successfully logged in.", fg="green")
|
|
|
|
|
|
|
|
|
|
|
|
@cli.command()
|
2023-12-07 19:41:30 +01:00
|
|
|
@click.argument("account", type=AccountParamType(), required=False)
|
2023-11-30 20:12:04 +01:00
|
|
|
def logout(account: str):
|
|
|
|
"""Log out of ACCOUNT, delete stored access keys"""
|
|
|
|
accounts = _get_accounts_list()
|
|
|
|
|
|
|
|
if not account:
|
|
|
|
raise click.ClickException(f"Specify account to log out:\n{accounts}")
|
|
|
|
|
|
|
|
user = config.load_user(account)
|
|
|
|
|
|
|
|
if not user:
|
|
|
|
raise click.ClickException(f"Account not found. Logged in accounts:\n{accounts}")
|
|
|
|
|
|
|
|
config.delete_user(user)
|
|
|
|
click.secho(f"✓ Account {account} logged out", fg="green")
|
|
|
|
|
|
|
|
|
|
|
|
@cli.command()
|
2023-12-07 19:41:30 +01:00
|
|
|
@click.argument("account", type=AccountParamType(), required=False)
|
2023-11-30 20:12:04 +01:00
|
|
|
def activate(account: str):
|
|
|
|
"""Switch to logged in ACCOUNT."""
|
|
|
|
accounts = _get_accounts_list()
|
|
|
|
|
|
|
|
if not account:
|
|
|
|
raise click.ClickException(f"Specify account to activate:\n{accounts}")
|
|
|
|
|
|
|
|
user = config.load_user(account)
|
|
|
|
|
|
|
|
if not user:
|
|
|
|
raise click.ClickException(f"Account not found. Logged in accounts:\n{accounts}")
|
|
|
|
|
|
|
|
config.activate_user(user)
|
|
|
|
click.secho(f"✓ Account {account} activated", fg="green")
|
|
|
|
|
|
|
|
|
|
|
|
def _get_accounts_list() -> str:
|
|
|
|
accounts = config.load_config()["users"].keys()
|
|
|
|
if not accounts:
|
|
|
|
raise click.ClickException("You're not logged into any accounts")
|
|
|
|
return "\n".join([f"* {acct}" for acct in accounts])
|