mirror of
https://github.com/ihabunek/toot.git
synced 2024-11-03 04:17:21 -05:00
parent
cebc88d329
commit
62c4075fe1
@ -4,6 +4,7 @@ Changelog
|
||||
**0.13.0 (TBA)**
|
||||
|
||||
* Allow passing `--instance` and `--email` to login command
|
||||
* Add `login_browser` command for proper two factor authentication through the browser (#19, #23)
|
||||
|
||||
**0.12.0 (2016-05-08)**
|
||||
|
||||
|
47
README.rst
47
README.rst
@ -37,29 +37,30 @@ Running ``toot <command> -h`` shows the documentation for the given command.
|
||||
toot - a Mastodon CLI client
|
||||
|
||||
Authentication:
|
||||
toot login Log into a Mastodon instance
|
||||
toot login_2fa Log in using two factor authentication (experimental)
|
||||
toot logout Log out, delete stored access keys
|
||||
toot auth Show stored credentials
|
||||
toot login Log into a Mastodon instance, does NOT support two factor authentication
|
||||
toot login_browser Log in using your browser, supports regular and two factor authentication
|
||||
toot login_2fa Log in using two factor authentication in the console (hacky, experimental)
|
||||
toot logout Log out, delete stored access keys
|
||||
toot auth Show stored credentials
|
||||
|
||||
Read:
|
||||
toot whoami Display logged in user details
|
||||
toot whois Display account details
|
||||
toot search Search for users or hashtags
|
||||
toot timeline Show recent items in your public timeline
|
||||
toot curses An experimental timeline app.
|
||||
toot whoami Display logged in user details
|
||||
toot whois Display account details
|
||||
toot search Search for users or hashtags
|
||||
toot timeline Show recent items in your public timeline
|
||||
toot curses An experimental timeline app.
|
||||
|
||||
Post:
|
||||
toot post Post a status text to your timeline
|
||||
toot upload Upload an image or video file
|
||||
toot post Post a status text to your timeline
|
||||
toot upload Upload an image or video file
|
||||
|
||||
Accounts:
|
||||
toot follow Follow an account
|
||||
toot unfollow Unfollow an account
|
||||
toot mute Mute an account
|
||||
toot unmute Unmute an account
|
||||
toot block Block an account
|
||||
toot unblock Unblock an account
|
||||
toot follow Follow an account
|
||||
toot unfollow Unfollow an account
|
||||
toot mute Mute an account
|
||||
toot unmute Unmute an account
|
||||
toot block Block an account
|
||||
toot unblock Unblock an account
|
||||
|
||||
To get help for each command run:
|
||||
toot <command> --help
|
||||
@ -77,19 +78,23 @@ It is possible to pipe status text into `toot post`, for example:
|
||||
Authentication
|
||||
--------------
|
||||
|
||||
Before tooting, you need to login to a Mastodon instance:
|
||||
Before tooting, you need to login to a Mastodon instance.
|
||||
|
||||
If you don't use two factor authentication you can log in directly from the command line:
|
||||
|
||||
.. code-block::
|
||||
|
||||
toot login
|
||||
|
||||
**Two factor authentication** is supported experimentally, instead of ``login``, you should instead run ``login_2fa``:
|
||||
You will be asked to chose an instance_ and enter your credentials.
|
||||
|
||||
If you do use **two factor authentication**, you need to log in through your browser:
|
||||
|
||||
.. code-block::
|
||||
|
||||
toot login_2fa
|
||||
toot login_browser
|
||||
|
||||
You will be asked to chose an instance_ and enter your credentials.
|
||||
You will be redirected to your Mastodon instance to log in and authorize toot to access your account, and will be given an **authorization code** in return which you need to enter to log in.
|
||||
|
||||
.. _instance: https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md
|
||||
|
||||
|
36
toot/api.py
36
toot/api.py
@ -4,7 +4,7 @@ import logging
|
||||
import re
|
||||
import requests
|
||||
|
||||
from future.moves.urllib.parse import urlparse
|
||||
from future.moves.urllib.parse import urlparse, urlencode
|
||||
from requests import Request, Session
|
||||
|
||||
from toot import CLIENT_NAME, CLIENT_WEBSITE
|
||||
@ -53,10 +53,16 @@ def _process_response(response):
|
||||
_log_response(response)
|
||||
|
||||
if not response.ok:
|
||||
error = "Unknown error"
|
||||
|
||||
try:
|
||||
error = response.json()['error']
|
||||
data = response.json()
|
||||
if "error_description" in data:
|
||||
error = data['error_description']
|
||||
elif "error" in data:
|
||||
error = data['error']
|
||||
except:
|
||||
error = "Unknown error"
|
||||
pass
|
||||
|
||||
if response.status_code == 404:
|
||||
raise NotFoundError(error)
|
||||
@ -131,6 +137,30 @@ def login(app, username, password):
|
||||
return _process_response(response).json()
|
||||
|
||||
|
||||
def get_browser_login_url(app):
|
||||
"""Returns the URL for manual log in via browser"""
|
||||
return "{}/oauth/authorize/?{}".format(app.base_url, urlencode({
|
||||
"response_type": "code",
|
||||
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
|
||||
"scope": "read write follow",
|
||||
"client_id": app.client_id,
|
||||
}))
|
||||
|
||||
|
||||
def request_access_token(app, authorization_code):
|
||||
url = app.base_url + '/oauth/token'
|
||||
|
||||
response = requests.post(url, {
|
||||
'grant_type': 'authorization_code',
|
||||
'client_id': app.client_id,
|
||||
'client_secret': app.client_secret,
|
||||
'code': authorization_code,
|
||||
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
|
||||
}, allow_redirects=False)
|
||||
|
||||
return _process_response(response).json()
|
||||
|
||||
|
||||
def post_status(app, user, status, visibility='public', media_ids=None):
|
||||
return _post(app, user, '/api/v1/statuses', {
|
||||
'status': status,
|
||||
|
@ -4,6 +4,7 @@ from __future__ import print_function
|
||||
|
||||
import json
|
||||
import requests
|
||||
import webbrowser
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from builtins import input
|
||||
@ -45,6 +46,15 @@ def create_app_interactive(instance=None):
|
||||
return config.load_app(instance) or register_app(instance)
|
||||
|
||||
|
||||
def create_user(app, email, access_token):
|
||||
user = User(app.instance, email, access_token)
|
||||
path = config.save_user(user)
|
||||
|
||||
print_out("Access token saved to: <green>{}</green>".format(path))
|
||||
|
||||
return user
|
||||
|
||||
|
||||
def login_interactive(app, email=None):
|
||||
print_out("Log in to <green>{}</green>".format(app.instance))
|
||||
|
||||
@ -62,12 +72,7 @@ def login_interactive(app, email=None):
|
||||
except api.ApiError:
|
||||
raise ConsoleError("Login failed")
|
||||
|
||||
user = User(app.instance, email, response['access_token'])
|
||||
path = config.save_user(user)
|
||||
|
||||
print_out("Access token saved to: <green>{}</green>".format(path))
|
||||
|
||||
return user
|
||||
return create_user(app, email, response['access_token'])
|
||||
|
||||
|
||||
def two_factor_login_interactive(app):
|
||||
@ -118,9 +123,7 @@ def two_factor_login_interactive(app):
|
||||
data = json.loads(initial_state.get_text())
|
||||
access_token = data['meta']['access_token']
|
||||
|
||||
user = User(app.instance, email, access_token)
|
||||
path = config.save_user(user)
|
||||
print_out("Access token saved to: <green>{}</green>".format(path))
|
||||
return create_user(app, email, access_token)
|
||||
|
||||
|
||||
def _print_timeline(item):
|
||||
@ -222,6 +225,46 @@ def login_2fa(app, user, args):
|
||||
print_out("<green>✓ Successfully logged in.</green>")
|
||||
|
||||
|
||||
BROWSER_LOGIN_EXPLANATION = """
|
||||
This authentication method requires you to log into your Mastodon instance
|
||||
in your browser, where you will be asked to authorize <yellow>toot</yellow> to access
|
||||
your account. When you do, you will be given an <yellow>authorization code</yellow>
|
||||
which you need to paste here.
|
||||
"""
|
||||
|
||||
|
||||
def login_browser(app, user, args):
|
||||
app = create_app_interactive(instance=args.instance)
|
||||
url = api.get_browser_login_url(app)
|
||||
|
||||
print_out(BROWSER_LOGIN_EXPLANATION)
|
||||
|
||||
print_out("This is the login URL:")
|
||||
print_out(url)
|
||||
print_out("")
|
||||
|
||||
yesno = input("Open link in default browser? [Y/n]")
|
||||
if not yesno or yesno.lower() == 'y':
|
||||
webbrowser.open(url)
|
||||
|
||||
authorization_code = ""
|
||||
while not authorization_code:
|
||||
authorization_code = input("Authorization code: ")
|
||||
|
||||
print_out("\nRequesting access token...")
|
||||
response = api.request_access_token(app, authorization_code)
|
||||
|
||||
# TODO: user email is not available in this workflow, maybe change the User
|
||||
# to store the username instead? Currently set to "unknown" since it's not
|
||||
# used anywhere.
|
||||
email = "unknown"
|
||||
|
||||
create_user(app, email, response['access_token'])
|
||||
|
||||
print_out()
|
||||
print_out("<green>✓ Successfully logged in.</green>")
|
||||
|
||||
|
||||
def logout(app, user, args):
|
||||
config.delete_user()
|
||||
|
||||
|
@ -38,26 +38,33 @@ account_arg = (["account"], {
|
||||
"help": "account name, e.g. 'Gargron' or 'polymerwitch@toot.cat'",
|
||||
})
|
||||
|
||||
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',
|
||||
})
|
||||
|
||||
|
||||
AUTH_COMMANDS = [
|
||||
Command(
|
||||
name="login",
|
||||
description="Log into a Mastodon instance",
|
||||
arguments=[
|
||||
(["-i", "--instance"], {
|
||||
"type": str,
|
||||
"help": 'mastodon instance to log into e.g. "mastodon.social"',
|
||||
}),
|
||||
(["-e", "--email"], {
|
||||
"type": str,
|
||||
"help": 'email address to log in with',
|
||||
}),
|
||||
],
|
||||
description="Log into a Mastodon instance, 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, email_arg],
|
||||
require_auth=False,
|
||||
),
|
||||
Command(
|
||||
name="login_2fa",
|
||||
description="Log in using two factor authentication (experimental)",
|
||||
description="Log in using two factor authentication in the console (hacky, experimental)",
|
||||
arguments=[],
|
||||
require_auth=False,
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user